diff --git a/AUTHORS b/AUTHORS
index 9778c49..47dc9b0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -67,6 +67,7 @@
 Ali Vathi <ali.akbar@gmail.com>
 Allan Sandfeld Jensen <allan.jensen@qt.io>
 Alper Çakan <alpercakan98@gmail.com>
+Alvaro Silva <alvaro.fagner@gmail.com>
 Ambarish Rapte <ambarish.r@samsung.com>
 Amey Jahagirdar <jahagird@amazon.com>
 Amit Sarkar <amit.srkr@samsung.com>
diff --git a/DEPS b/DEPS
index 51640850..fcd7ddeb 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '0cb3b8280b52b7d61c06ef2d62c5e5b1bd34a50d',
+  'skia_revision': '141b38bd06415044fdd25c51c54e2969ff2977a9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -288,7 +288,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': '6d08302be136ec5a68f6c7731644ad2c33e511e6',
+  'devtools_frontend_revision': '7539f18bfc2d6384e82cf5f48e5c03a4a5ace1a0',
   # 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.
@@ -643,7 +643,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'mK6h3vPf42deGRLcNhMs86C7kLFqRBbVdpj6je2B5l4C',
+          'version': '0WzFihpopxR1w_M_JdS1KEV120sU7SH4DqrFLio6DwsC',
         },
       ],
       'dep_type': 'cipd',
@@ -654,7 +654,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'rqMEoeQYWMnizGZkIjhiqqVSq1qopmEoG1_Enf0TzpoC',
+          'version': '5J3jubFQSDgGykGaj41wP1woSqgP8z7VRs2fhqcWP5kC',
         },
       ],
       'dep_type': 'cipd',
@@ -665,7 +665,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': '7JwhT_B07YI8j600Y1b8sWxc0z-9agB2f0yas982MaoC',
+          'version': 'c9OGWkeZV5V4P15nPwYbPdmQ1uVEGoXcaUvT2M6pzWcC',
         },
       ],
       'dep_type': 'cipd',
@@ -730,7 +730,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'RjeRyfAqXQf6VfPsfovn5_088ot2hkVyL1FaGWMsxI0C',
+          'version': 'XlmeAzahzz9NitTuWACJceeLF4HYYTJOmA4X__MlSbYC',
       },
     ],
     'condition': 'checkout_android',
@@ -946,7 +946,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c1fed741f4b199ee0f3ee424ea28be059877608a',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd19ad38cca71fa77441a8a91b4b6b4b9ded3ba75',
       'condition': 'checkout_chromeos',
   },
 
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3da91715d3b7447cc012b3e38901b9e9a54039eb',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '75229247494ecf31997b68bf143264e1537f92e0',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1416,7 +1416,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': '42zBK32YxEB18xY1NCrwmj25vLkVlpXgaCil3aOhi6EC'
+              'version': 'QpXtlWg0Hrksvqhm2JkK5cg7xWznHgNj3aHSgOF-cCkC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1552,7 +1552,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '239db71432f4e4fe1f6192a7d54717701ef84f66',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'c51ce06208e516ce80539a100a5d1f41ff360c75',
+    Var('webrtc_git') + '/src.git' + '@' + '1c5c2178fed4068a94c4d3a71a7da428cac22151',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1632,7 +1632,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'HrL-PzIgGG9Ditn5-3SSdItiYijy3Gpdv8BG7FmvKWMC',
+        'version': '81loczAdGywrK_n3itMiMcHc1DbpiXE9jo_Jh55L30MC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1643,7 +1643,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'yq2wiZvZwXiTKDSQaIwCXw5lybPYtMXTyVf9nUElaXEC',
+        'version': '7SMVF8qXvaDt4slD9RY8mLYUjzLipNAWyfbubZuNPBcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index f25e0f7..d25fa1b 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -58,6 +58,10 @@
 const base::Feature kWebViewOriginTrials{"WebViewOriginTrials",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables package name logging for the most popular WebView embedders.
+const base::Feature kWebViewPackageNameLogging{
+    "WebViewPackageNameLogging", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Restricts all of WebView's out-of-process renderer threads to use only LITTLE
 // cores on big.LITTLE architectures when the power mode is idle.
 const base::Feature kWebViewPowerSchedulerThrottleIdle{
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 9a3ca61..21f6973e 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -23,6 +23,7 @@
 extern const base::Feature kWebViewMeasureScreenCoverage;
 extern const base::Feature kWebViewMixedContentAutoupgrades;
 extern const base::Feature kWebViewOriginTrials;
+extern const base::Feature kWebViewPackageNameLogging;
 extern const base::Feature kWebViewPowerSchedulerThrottleIdle;
 extern const base::Feature kWebViewSuppressDifferentOriginSubframeJSDialogs;
 extern const base::Feature kWebViewTestFeature;
diff --git a/ash/app_list/model/app_list_item.cc b/ash/app_list/model/app_list_item.cc
index 3c2964e8..4403a986 100644
--- a/ash/app_list/model/app_list_item.cc
+++ b/ash/app_list/model/app_list_item.cc
@@ -61,10 +61,7 @@
 }
 
 void AppListItem::SetNotificationBadgeColor(const SkColor color) {
-  if (notification_badge_color_ == color)
-    return;
-
-  notification_badge_color_ = color;
+  metadata_->badge_color = color;
   for (auto& observer : observers_) {
     observer.ItemBadgeColorChanged();
   }
diff --git a/ash/app_list/model/app_list_item.h b/ash/app_list/model/app_list_item.h
index 6f65a26..cfe57c0 100644
--- a/ash/app_list/model/app_list_item.h
+++ b/ash/app_list/model/app_list_item.h
@@ -95,7 +95,7 @@
 
   bool has_notification_badge() const { return has_notification_badge_; }
 
-  SkColor notification_badge_color() const { return notification_badge_color_; }
+  SkColor notification_badge_color() const { return metadata_->badge_color; }
 
   void UpdateNotificationBadgeForTesting(bool has_badge) {
     UpdateNotificationBadge(has_badge);
@@ -158,9 +158,6 @@
   // Whether this item currently has a notification badge that should be shown.
   bool has_notification_badge_ = false;
 
-  // The color for the notification badge displayed over the app icon.
-  SkColor notification_badge_color_ = SK_ColorWHITE;
-
   base::ObserverList<AppListItemObserver> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(AppListItem);
diff --git a/ash/app_list/views/page_switcher.cc b/ash/app_list/views/page_switcher.cc
index bdd1d7f..142c3d5ce 100644
--- a/ash/app_list/views/page_switcher.cc
+++ b/ash/app_list/views/page_switcher.cc
@@ -57,10 +57,26 @@
     SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
     SetInkDropMode(InkDropMode::ON);
     views::InkDrop::UseInkDropForFloodFillRipple(this);
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](PageSwitcherButton* host) {
+          const AppListColorProvider* const color_provider =
+              AppListColorProvider::Get();
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()),
+              color_provider->GetRippleAttributesBaseColor(
+                  host->background_color_));
+          highlight->set_visible_opacity(
+              color_provider->GetRippleAttributesHighlightOpacity(
+                  host->background_color_));
+          return highlight;
+        },
+        this));
+
     views::InstallFixedSizeCircleHighlightPathGenerator(
         this, is_root_app_grid_page_switcher ? kInkDropRadiusForRootGrid
                                              : kInkDropRadiusForFolderGrid);
   }
+
   PageSwitcherButton(const PageSwitcherButton&) = delete;
   PageSwitcherButton& operator=(const PageSwitcherButton&) = delete;
 
@@ -104,17 +120,6 @@
         color_provider->GetRippleAttributesInkDropOpacity(background_color_));
   }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    const AppListColorProvider* color_provider = AppListColorProvider::Get();
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(size()),
-        color_provider->GetRippleAttributesBaseColor(background_color_));
-    highlight->set_visible_opacity(
-        color_provider->GetRippleAttributesHighlightOpacity(background_color_));
-    return highlight;
-  }
-
   void NotifyClick(const ui::Event& event) override {
     Button::NotifyClick(event);
     GetInkDrop()->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
diff --git a/ash/app_list/views/search_result_actions_view.cc b/ash/app_list/views/search_result_actions_view.cc
index 06f43c6..e6794ec 100644
--- a/ash/app_list/views/search_result_actions_view.cc
+++ b/ash/app_list/views/search_result_actions_view.cc
@@ -13,6 +13,7 @@
 #include "ash/app_list/views/search_result_view.h"
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
+#include "base/bind.h"
 #include "base/numerics/ranges.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/canvas.h"
@@ -50,8 +51,6 @@
 
   // views::InkDropHostView:
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
 
   // Updates the button visibility upon state change of the button or the
   // search result view associated with it.
@@ -82,6 +81,19 @@
   // OnPaintBackground();
   SetFocusPainter(nullptr);
   SetInkDropMode(InkDropMode::ON);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](SearchResultImageButton* host) {
+        const AppListColorProvider* const color_provider =
+            AppListColorProvider::Get();
+        const SkColor bg_color = color_provider->GetSearchBoxBackgroundColor();
+        auto highlight = std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(host->size()),
+            color_provider->GetRippleAttributesBaseColor(bg_color));
+        highlight->set_visible_opacity(
+            color_provider->GetRippleAttributesHighlightOpacity(bg_color));
+        return highlight;
+      },
+      this));
 
   SetPreferredSize({kImageButtonSizeDip, kImageButtonSizeDip});
   SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
@@ -134,18 +146,6 @@
       color_provider->GetRippleAttributesInkDropOpacity(bg_color));
 }
 
-std::unique_ptr<views::InkDropHighlight>
-SearchResultImageButton::CreateInkDropHighlight() const {
-  const AppListColorProvider* color_provider = AppListColorProvider::Get();
-  const SkColor bg_color = color_provider->GetSearchBoxBackgroundColor();
-  auto highlight = std::make_unique<views::InkDropHighlight>(
-      gfx::SizeF(size()),
-      color_provider->GetRippleAttributesBaseColor(bg_color));
-  highlight->set_visible_opacity(
-      color_provider->GetRippleAttributesHighlightOpacity(bg_color));
-  return highlight;
-}
-
 void SearchResultImageButton::UpdateOnStateChanged() {
   // Show button if the associated result row is hovered or selected, or one
   // of the action buttons is selected.
diff --git a/ash/assistant/ui/base/assistant_button.cc b/ash/assistant/ui/base/assistant_button.cc
index db1b2bd..854d51fd 100644
--- a/ash/assistant/ui/base/assistant_button.cc
+++ b/ash/assistant/ui/base/assistant_button.cc
@@ -59,6 +59,14 @@
   SetInkDropVisibleOpacity(kInkDropVisibleOpacity);
   views::InstallCircleHighlightPathGenerator(this, gfx::Insets(kInkDropInset));
   views::InkDrop::UseInkDropForFloodFillRipple(this);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](InkDropHostView* host) {
+        auto highlight = std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(host->size()), host->GetInkDropBaseColor());
+        highlight->set_visible_opacity(kInkDropHighlightOpacity);
+        return highlight;
+      },
+      this));
 }
 
 AssistantButton::~AssistantButton() = default;
@@ -97,14 +105,6 @@
       width() / 2 - kInkDropInset, gfx::Insets(kInkDropInset)));
 }
 
-std::unique_ptr<views::InkDropHighlight>
-AssistantButton::CreateInkDropHighlight() const {
-  auto highlight = std::make_unique<views::InkDropHighlight>(
-      gfx::SizeF(size()), GetInkDropBaseColor());
-  highlight->set_visible_opacity(kInkDropHighlightOpacity);
-  return highlight;
-}
-
 std::unique_ptr<views::InkDropRipple> AssistantButton::CreateInkDropRipple()
     const {
   return std::make_unique<views::FloodFillInkDropRipple>(
diff --git a/ash/assistant/ui/base/assistant_button.h b/ash/assistant/ui/base/assistant_button.h
index c0263af..59c4805 100644
--- a/ash/assistant/ui/base/assistant_button.h
+++ b/ash/assistant/ui/base/assistant_button.h
@@ -73,8 +73,6 @@
 
   // views::ImageButton:
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
 
  private:
diff --git a/ash/capture_mode/view_with_ink_drop.h b/ash/capture_mode/view_with_ink_drop.h
index 00b1110..38d51b1 100644
--- a/ash/capture_mode/view_with_ink_drop.h
+++ b/ash/capture_mode/view_with_ink_drop.h
@@ -8,6 +8,7 @@
 #include <type_traits>
 
 #include "ash/capture_mode/capture_mode_constants.h"
+#include "base/bind.h"
 #include "ui/views/animation/ink_drop_host_view.h"
 #include "ui/views/animation/ink_drop_impl.h"
 
@@ -37,20 +38,19 @@
     views::InkDrop::UseInkDropForFloodFillRipple(this,
                                                  /*highlight_on_hover=*/false,
                                                  /*highlight_on_focus=*/false);
+    T::SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](views::InkDropHostView* host) {
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()), host->GetInkDropBaseColor());
+          highlight->set_visible_opacity(
+              capture_mode::kInkDropHighlightVisibleOpacity);
+          return highlight;
+        },
+        this));
   }
 
   ~ViewWithInkDrop() override = default;
 
-  // views::InkDropHostView:
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(T::size()), GetInkDropBaseColor());
-    highlight->set_visible_opacity(
-        capture_mode::kInkDropHighlightVisibleOpacity);
-    return highlight;
-  }
-
   SkColor GetInkDropBaseColor() const override {
     return capture_mode::kInkDropBaseColor;
   }
diff --git a/ash/login/ui/lock_screen_media_controls_view.cc b/ash/login/ui/lock_screen_media_controls_view.cc
index d995323e..f9bf7cb0f 100644
--- a/ash/login/ui/lock_screen_media_controls_view.cc
+++ b/ash/login/ui/lock_screen_media_controls_view.cc
@@ -14,6 +14,7 @@
 #include "ash/shell_delegate.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "base/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -155,6 +156,13 @@
         icon_size_(icon_size) {
     SetInkDropMode(views::Button::InkDropMode::ON);
     SetHasInkDropActionOnClick(true);
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](InkDropHostView* host) {
+          return std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()), host->GetInkDropBaseColor());
+        },
+        this));
+
     SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
     SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
     SetBorder(
@@ -182,12 +190,6 @@
     UpdateIcon();
   }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    return std::make_unique<views::InkDropHighlight>(gfx::SizeF(size()),
-                                                     GetInkDropBaseColor());
-  }
-
   // views::View:
   void OnThemeChanged() override {
     views::View::OnThemeChanged();
diff --git a/ash/login/ui/login_button.cc b/ash/login/ui/login_button.cc
index 71d5bd6..91cb196 100644
--- a/ash/login/ui/login_button.cc
+++ b/ash/login/ui/login_button.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "ash/login/ui/views_utils.h"
+#include "base/bind.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 
@@ -29,6 +30,12 @@
   SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
   SetInkDropMode(InkDropMode::ON);
   SetHasInkDropActionOnClick(true);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](InkDropHostView* host) {
+        return std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(host->size()), kInkDropHighlightColor);
+      },
+      this));
 
   SetInstallFocusRingOnFocus(true);
   login_views_utils::ConfigureRectFocusRingCircleInkDrop(this, focus_ring(),
@@ -49,12 +56,6 @@
       1.f /*visible_opacity*/);
 }
 
-std::unique_ptr<views::InkDropHighlight> LoginButton::CreateInkDropHighlight()
-    const {
-  return std::make_unique<views::InkDropHighlight>(gfx::SizeF(size()),
-                                                   kInkDropHighlightColor);
-}
-
 int LoginButton::GetInkDropRadius() const {
   return std::min(GetLocalBounds().width(), GetLocalBounds().height()) / 2;
 }
diff --git a/ash/login/ui/login_button.h b/ash/login/ui/login_button.h
index a01d903..aab2acbb 100644
--- a/ash/login/ui/login_button.h
+++ b/ash/login/ui/login_button.h
@@ -19,8 +19,6 @@
 
   // views::InkDropHost:
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
 
  protected:
   virtual int GetInkDropRadius() const;
diff --git a/ash/login/ui/login_pin_view.cc b/ash/login/ui/login_pin_view.cc
index b47d3bb..b15101e 100644
--- a/ash/login/ui/login_pin_view.cc
+++ b/ash/login/ui/login_pin_view.cc
@@ -107,6 +107,15 @@
     SetPaintToLayer();
     layer()->SetFillsBoundsOpaquely(false);
     SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](BasePinButton* host) {
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()),
+              host->palette_.pin_ink_drop_highlight_color);
+          highlight->set_visible_opacity(1.0f);
+          return highlight;
+        },
+        this));
 
     views::FocusRing* focus_ring = views::FocusRing::Install(this);
     login_views_utils::ConfigureRectFocusRingCircleInkDrop(
@@ -160,14 +169,6 @@
         GetInkDropCenterBasedOnLastEvent(), palette_.pin_ink_drop_ripple_color,
         /*visible_opacity=*/1.f);
   }
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(size()), palette_.pin_ink_drop_highlight_color);
-    highlight->set_visible_opacity(1.f);
-    return highlight;
-  }
-
  protected:
   // Called when the button has been pressed.
   virtual void DispatchPress(ui::Event* event) {
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index a02fc4a1..bcd34e2 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -382,6 +382,7 @@
     "file_icon_util_unittest.cc",
     "holding_space/holding_space_image_unittest.cc",
     "holding_space/holding_space_item_unittest.cc",
+    "holding_space/holding_space_model_unittest.cc",
     "metrics_util_unittest.cc",
     "pagination/pagination_model_unittest.cc",
     "power_utils_unittest.cc",
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index a25c107..538f36b8 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -73,6 +73,7 @@
                                // 1 item.
   gfx::ImageSkia icon;         // The icon of this item.
   bool is_page_break = false;  // Whether this item is a "page break" item.
+  SkColor badge_color = SK_ColorWHITE;  // Notification badge color.
 };
 
 // All possible states of the app list.
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
index 198d9b3..a1fca9c9 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
@@ -157,6 +157,7 @@
       // separately in ArcNotificationItemImpl.
       if (event->type() == ui::ET_MOUSE_RELEASED ||
           event->type() == ui::ET_GESTURE_TAP) {
+        // TODO(b/185943161): Record this in arc::ArcMetricsService.
         UMA_HISTOGRAM_ENUMERATION(
             "Arc.UserInteraction",
             arc::UserInteractionType::NOTIFICATION_INTERACTION);
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc b/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
index 7d1a564..3d3197ae 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
@@ -184,6 +184,7 @@
   // This is reached when user focuses on the notification and hits enter on
   // keyboard. Mouse clicks and taps are handled separately in
   // ArcNotificationContentView.
+  // TODO(b/185943161): Record this in arc::ArcMetricsService.
   UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
                             arc::UserInteractionType::NOTIFICATION_INTERACTION);
 }
diff --git a/ash/public/cpp/holding_space/holding_space_item.cc b/ash/public/cpp/holding_space/holding_space_item.cc
index ec623862..2e1c8fb 100644
--- a/ash/public/cpp/holding_space/holding_space_item.cc
+++ b/ash/public/cpp/holding_space/holding_space_item.cc
@@ -37,7 +37,7 @@
 bool HoldingSpaceItem::operator==(const HoldingSpaceItem& rhs) const {
   return type_ == rhs.type_ && id_ == rhs.id_ && file_path_ == rhs.file_path_ &&
          file_system_url_ == rhs.file_system_url_ && text_ == rhs.text_ &&
-         *image_ == *rhs.image_;
+         *image_ == *rhs.image_ && progress_ == rhs.progress_;
 }
 
 // static
@@ -46,13 +46,24 @@
     const base::FilePath& file_path,
     const GURL& file_system_url,
     ImageResolver image_resolver) {
+  return CreateFileBackedItem(type, file_path, file_system_url,
+                              /*progress=*/1.f, std::move(image_resolver));
+}
+
+// static
+std::unique_ptr<HoldingSpaceItem> HoldingSpaceItem::CreateFileBackedItem(
+    Type type,
+    const base::FilePath& file_path,
+    const GURL& file_system_url,
+    const base::Optional<float>& progress,
+    ImageResolver image_resolver) {
   DCHECK(!file_system_url.is_empty());
 
   // Note: std::make_unique does not work with private constructors.
   return base::WrapUnique(new HoldingSpaceItem(
       type, /*id=*/base::UnguessableToken::Create().ToString(), file_path,
       file_system_url, file_path.BaseName().LossyDisplayName(),
-      std::move(image_resolver).Run(type, file_path)));
+      std::move(image_resolver).Run(type, file_path), progress));
 }
 
 // static
@@ -86,7 +97,7 @@
   return base::WrapUnique(new HoldingSpaceItem(
       type, DeserializeId(dict), file_path,
       /*file_system_url=*/GURL(), file_path.BaseName().LossyDisplayName(),
-      std::move(image_resolver).Run(type, file_path)));
+      std::move(image_resolver).Run(type, file_path), /*progress=*/1.f));
 }
 
 // static
@@ -145,12 +156,31 @@
   file_system_url_ = file_system_url;
 }
 
-void HoldingSpaceItem::UpdateBackingFile(const base::FilePath& file_path,
+bool HoldingSpaceItem::UpdateBackingFile(const base::FilePath& file_path,
                                          const GURL& file_system_url) {
+  if (file_path_ == file_path && file_system_url_ == file_system_url)
+    return false;
+
   file_path_ = file_path;
   file_system_url_ = file_system_url;
   text_ = file_path.BaseName().LossyDisplayName();
   image_->UpdateBackingFilePath(file_path);
+
+  return true;
+}
+
+bool HoldingSpaceItem::UpdateProgress(const base::Optional<float>& progress) {
+  // NOTE: Once set to `1.f`, `progress_` becomes read-only.
+  if (progress_ == progress || progress_ == 1.f)
+    return false;
+
+  if (progress.has_value()) {
+    DCHECK_GE(progress.value(), 0.f);
+    DCHECK_LE(progress.value(), 1.f);
+  }
+
+  progress_ = progress;
+  return true;
 }
 
 void HoldingSpaceItem::InvalidateImage() {
@@ -177,12 +207,19 @@
                                    const base::FilePath& file_path,
                                    const GURL& file_system_url,
                                    const std::u16string& text,
-                                   std::unique_ptr<HoldingSpaceImage> image)
+                                   std::unique_ptr<HoldingSpaceImage> image,
+                                   const base::Optional<float>& progress)
     : type_(type),
       id_(id),
       file_path_(file_path),
       file_system_url_(file_system_url),
       text_(text),
-      image_(std::move(image)) {}
+      image_(std::move(image)),
+      progress_(progress) {
+  if (progress_.has_value()) {
+    DCHECK_GE(progress_.value(), 0.f);
+    DCHECK_LE(progress_.value(), 1.f);
+  }
+}
 
 }  // namespace ash
diff --git a/ash/public/cpp/holding_space/holding_space_item.h b/ash/public/cpp/holding_space/holding_space_item.h
index a9e0139d..235abbb 100644
--- a/ash/public/cpp/holding_space/holding_space_item.h
+++ b/ash/public/cpp/holding_space/holding_space_item.h
@@ -12,6 +12,7 @@
 #include "base/callback_forward.h"
 #include "base/callback_list.h"
 #include "base/files/file_path.h"
+#include "base/optional.h"
 #include "url/gurl.h"
 
 namespace base {
@@ -22,8 +23,7 @@
 
 class HoldingSpaceImage;
 
-// Contains data needed to display a single item in the temporary holding space
-// UI.
+// Contains data needed to display a single item in the holding space UI.
 class ASH_PUBLIC_EXPORT HoldingSpaceItem {
  public:
   // Items types supported by the holding space.
@@ -59,6 +59,16 @@
       const GURL& file_system_url,
       ImageResolver image_resolver);
 
+  // Creates a HoldingSpaceItem that's backed by a file system URL.
+  // NOTE: `file_system_url` is expected to be non-empty.
+  // NOTE: If present, `progress` must be >= `0.f` and <= `1.f`.
+  static std::unique_ptr<HoldingSpaceItem> CreateFileBackedItem(
+      Type type,
+      const base::FilePath& file_path,
+      const GURL& file_system_url,
+      const base::Optional<float>& progress,
+      ImageResolver image_resolver);
+
   // Returns `true` if `type` is a download type, `false` otherwise.
   static bool IsDownload(HoldingSpaceItem::Type type);
 
@@ -91,10 +101,16 @@
   // `Deserialize()`.
   void Initialize(const GURL& file_system_url);
 
-  // Updates the file backing the item to `file_path` and `file_system_url`.
-  void UpdateBackingFile(const base::FilePath& file_path,
+  // Updates the file backing the item to `file_path` and `file_system_url`,
+  // returning `false` to indicate no-op.
+  bool UpdateBackingFile(const base::FilePath& file_path,
                          const GURL& file_system_url);
 
+  // Updates the `progress_` of the item, returning `false` to indicate no-op.
+  // NOTE: If present, `progress` must be >= `0.f` and <= `1.f`.
+  // NOTE: Once set to `1.f`, `progress_` becomes read-only.
+  bool UpdateProgress(const base::Optional<float>& progress);
+
   // Invalidates the current holding space image, so fresh image representations
   // are loaded when the image is next needed.
   void InvalidateImage();
@@ -114,6 +130,8 @@
 
   const GURL& file_system_url() const { return file_system_url_; }
 
+  const base::Optional<float>& progress() const { return progress_; }
+
   HoldingSpaceImage& image_for_testing() { return *image_; }
 
  private:
@@ -123,7 +141,8 @@
                    const base::FilePath& file_path,
                    const GURL& file_system_url,
                    const std::u16string& text,
-                   std::unique_ptr<HoldingSpaceImage> image);
+                   std::unique_ptr<HoldingSpaceImage> image,
+                   const base::Optional<float>& progress);
 
   const Type type_;
 
@@ -142,6 +161,11 @@
   // The image representation of the item.
   std::unique_ptr<HoldingSpaceImage> image_;
 
+  // The progress of the item.
+  // If present, the value is >= `0.f` and <= `1.f`.
+  // If absent, `progress_` is indeterminate.
+  base::Optional<float> progress_;
+
   // Mutable to allow const access from `AddDeletionCallback()`.
   mutable base::RepeatingClosureList deletion_callback_list_;
 };
diff --git a/ash/public/cpp/holding_space/holding_space_item_unittest.cc b/ash/public/cpp/holding_space/holding_space_item_unittest.cc
index c6be05e..cf5d90a 100644
--- a/ash/public/cpp/holding_space/holding_space_item_unittest.cc
+++ b/ash/public/cpp/holding_space/holding_space_item_unittest.cc
@@ -76,6 +76,51 @@
   EXPECT_EQ(deserialized_holding_space_id, holding_space_item->id());
 }
 
+// Tests progress for each holding space item type.
+TEST_P(HoldingSpaceItemTest, Progress) {
+  // Create a `holding_space_item` w/ explicitly specified progress.
+  auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
+      /*type=*/GetParam(), base::FilePath("file_path"),
+      GURL("filesystem::file_system_url"), /*progress=*/0.5f,
+      /*image_resolver=*/base::BindOnce(&CreateFakeHoldingSpaceImage));
+
+  // Since explicitly specified during construction, progress should be `0.5`.
+  EXPECT_EQ(holding_space_item->progress(), 0.5f);
+
+  // It should be possible to update progress to a new value.
+  EXPECT_TRUE(holding_space_item->UpdateProgress(0.75f));
+  EXPECT_EQ(holding_space_item->progress(), 0.75f);
+
+  // It should no-op to try to update progress to its existing value.
+  EXPECT_FALSE(holding_space_item->UpdateProgress(0.75f));
+  EXPECT_EQ(holding_space_item->progress(), 0.75f);
+
+  // It should be possible to set indeterminate progress.
+  EXPECT_TRUE(holding_space_item->UpdateProgress(base::nullopt));
+  EXPECT_EQ(holding_space_item->progress(), base::nullopt);
+
+  // It should be possible to set progress complete.
+  EXPECT_TRUE(holding_space_item->UpdateProgress(1.f));
+  EXPECT_EQ(holding_space_item->progress(), 1.f);
+
+  // Once progress has been marked completed, it should become read-only.
+  EXPECT_FALSE(holding_space_item->UpdateProgress(0.75f));
+  EXPECT_EQ(holding_space_item->progress(), 1.f);
+
+  // Create a `holding_space_item` w/ default progress.
+  holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
+      /*type=*/GetParam(), base::FilePath("file_path"),
+      GURL("filesystem::file_system_url"),
+      /*image_resolver=*/base::BindOnce(&CreateFakeHoldingSpaceImage));
+
+  // Since not specified during construction, progress should be `1.f`.
+  EXPECT_EQ(holding_space_item->progress(), 1.f);
+
+  // Since progress is marked completed, it should be read-only.
+  EXPECT_FALSE(holding_space_item->UpdateProgress(0.75f));
+  EXPECT_EQ(holding_space_item->progress(), 1.f);
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          HoldingSpaceItemTest,
                          testing::ValuesIn(GetHoldingSpaceItemTypes()));
diff --git a/ash/public/cpp/holding_space/holding_space_model.cc b/ash/public/cpp/holding_space/holding_space_model.cc
index 7bb22d5..a078bfb3 100644
--- a/ash/public/cpp/holding_space/holding_space_model.cc
+++ b/ash/public/cpp/holding_space/holding_space_model.cc
@@ -88,7 +88,28 @@
   HoldingSpaceItem* item = item_it->get();
   DCHECK(item->IsInitialized());
 
-  item->UpdateBackingFile(file_path, file_system_url);
+  if (!item->UpdateBackingFile(file_path, file_system_url))
+    return;
+
+  for (auto& observer : observers_)
+    observer.OnHoldingSpaceItemUpdated(item);
+}
+
+void HoldingSpaceModel::UpdateProgressForItem(
+    const std::string& id,
+    const base::Optional<float>& progress) {
+  auto item_it = std::find_if(
+      items_.begin(), items_.end(),
+      [&id](const std::unique_ptr<HoldingSpaceItem>& item) -> bool {
+        return item->id() == id;
+      });
+  DCHECK(item_it != items_.end());
+
+  HoldingSpaceItem* item = item_it->get();
+  DCHECK(item->IsInitialized());
+
+  if (!item->UpdateProgress(progress))
+    return;
 
   for (auto& observer : observers_)
     observer.OnHoldingSpaceItemUpdated(item);
diff --git a/ash/public/cpp/holding_space/holding_space_model.h b/ash/public/cpp/holding_space/holding_space_model.h
index 41066469..b7134d1f 100644
--- a/ash/public/cpp/holding_space/holding_space_model.h
+++ b/ash/public/cpp/holding_space/holding_space_model.h
@@ -15,6 +15,7 @@
 #include "ash/public/cpp/holding_space/holding_space_item.h"
 #include "base/callback.h"
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "url/gurl.h"
 
 namespace base {
@@ -65,6 +66,12 @@
                                 const base::FilePath& file_path,
                                 const GURL& file_system_url);
 
+  // Updates the progress for a single holding space item.
+  // NOTE: If present, `progress` must be >= `0.f` and <= `1.f`.
+  // NOTE: Once set to `1.f`, holding space item progress becomes read-only.
+  void UpdateProgressForItem(const std::string& id,
+                             const base::Optional<float>& progress);
+
   // Removes all holding space items from the model for which the specified
   // `predicate` returns true.
   using Predicate = base::RepeatingCallback<bool(const HoldingSpaceItem*)>;
diff --git a/ash/public/cpp/holding_space/holding_space_model_unittest.cc b/ash/public/cpp/holding_space/holding_space_model_unittest.cc
new file mode 100644
index 0000000..4944d99
--- /dev/null
+++ b/ash/public/cpp/holding_space/holding_space_model_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/holding_space/holding_space_model.h"
+
+#include <memory>
+#include <vector>
+
+#include "ash/public/cpp/holding_space/holding_space_image.h"
+#include "ash/public/cpp/holding_space/holding_space_item.h"
+#include "ash/public/cpp/holding_space/holding_space_model_observer.h"
+#include "base/scoped_observation.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+// Helpers ---------------------------------------------------------------------
+
+std::vector<HoldingSpaceItem::Type> GetHoldingSpaceItemTypes() {
+  std::vector<HoldingSpaceItem::Type> types;
+  for (int i = 0; i <= static_cast<int>(HoldingSpaceItem::Type::kMaxValue); ++i)
+    types.push_back(static_cast<HoldingSpaceItem::Type>(i));
+  return types;
+}
+
+std::unique_ptr<HoldingSpaceImage> CreateFakeHoldingSpaceImage(
+    HoldingSpaceItem::Type type,
+    const base::FilePath& file_path) {
+  return std::make_unique<HoldingSpaceImage>(
+      HoldingSpaceImage::GetMaxSizeForType(type), file_path,
+      /*async_bitmap_resolver=*/base::DoNothing());
+}
+
+// ScopedModelObservation ------------------------------------------------------
+
+// A class which observes a `HoldingSpaceModel` within its lifetime. Note that
+// this class must not outlive the `model` that it observes.
+class ScopedModelObservation : public HoldingSpaceModelObserver {
+ public:
+  explicit ScopedModelObservation(HoldingSpaceModel* model) {
+    observation_.Observe(model);
+  }
+
+  ScopedModelObservation(const ScopedModelObservation&) = delete;
+  ScopedModelObservation& operator=(const ScopedModelObservation&) = delete;
+  ~ScopedModelObservation() override = default;
+
+  // Returns the last `HoldingSpaceItem` for which `OnHoldingSpaceItemUpdated()`
+  // was called, clearing the cached value.
+  const HoldingSpaceItem* TakeLastUpdatedItem() {
+    const HoldingSpaceItem* result = last_updated_item_;
+    last_updated_item_ = nullptr;
+    return result;
+  }
+
+ private:
+  // HoldingSpaceModel::Observer:
+  void OnHoldingSpaceItemUpdated(const HoldingSpaceItem* item) override {
+    last_updated_item_ = item;
+  }
+
+  // The last `HoldingSpaceItem` for which `OnHoldingSpaceItemUpdated()` was
+  // called. May be `nullptr` prior to an update event or following a call to
+  // `TakeLastUpdatedItem()`.
+  const HoldingSpaceItem* last_updated_item_ = nullptr;
+
+  base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
+      observation_{this};
+};
+
+}  // namespace
+
+// HoldingSpaceModelTest -------------------------------------------------------
+
+// Base class for `HoldingSpaceModel` tests, parameterized by the set of all
+// holding space item types.
+class HoldingSpaceModelTest
+    : public testing::TestWithParam<HoldingSpaceItem::Type> {
+ public:
+  // Returns the `HoldingSpaceModel` under test.
+  HoldingSpaceModel& model() { return model_; }
+
+ private:
+  HoldingSpaceModel model_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         HoldingSpaceModelTest,
+                         testing::ValuesIn(GetHoldingSpaceItemTypes()));
+
+// Tests -----------------------------------------------------------------------
+
+// Verifies that `HoldingSpaceModel::UpdateProgressForItem()` works as intended.
+TEST_P(HoldingSpaceModelTest, UpdateProgressForItem) {
+  ScopedModelObservation observation(&model());
+
+  // Verify the `model()` is initially empty.
+  EXPECT_EQ(model().items().size(), 0u);
+
+  // Create a holding space `item`.
+  auto item = HoldingSpaceItem::CreateFileBackedItem(
+      /*type=*/GetParam(), base::FilePath("file_path"),
+      GURL("filesystem::file_system_url"),
+      /*progress=*/base::nullopt,
+      /*image_resolver=*/base::BindOnce(&CreateFakeHoldingSpaceImage));
+  auto* item_ptr = item.get();
+
+  // Add `item` to the `model()`.
+  model().AddItem(std::move(item));
+  EXPECT_EQ(model().items().size(), 1u);
+  EXPECT_EQ(model().items()[0].get(), item_ptr);
+
+  // Verify progress is indeterminate.
+  EXPECT_EQ(item_ptr->progress(), base::nullopt);
+
+  // Update progress to `0.5f`.
+  model().UpdateProgressForItem(item_ptr->id(), 0.5f);
+  EXPECT_EQ(observation.TakeLastUpdatedItem(), item_ptr);
+  EXPECT_EQ(item_ptr->progress(), 0.5f);
+
+  // Update progress to `0.5f` again. This should no-op.
+  model().UpdateProgressForItem(item_ptr->id(), 0.5f);
+  EXPECT_FALSE(observation.TakeLastUpdatedItem());
+  EXPECT_EQ(item_ptr->progress(), 0.5f);
+
+  // Update progress to indeterminate.
+  model().UpdateProgressForItem(item_ptr->id(), base::nullopt);
+  EXPECT_EQ(observation.TakeLastUpdatedItem(), item_ptr);
+  EXPECT_EQ(item_ptr->progress(), base::nullopt);
+
+  // Update progress to complete.
+  model().UpdateProgressForItem(item_ptr->id(), 1.f);
+  EXPECT_EQ(observation.TakeLastUpdatedItem(), item_ptr);
+  EXPECT_EQ(item_ptr->progress(), 1.f);
+
+  // Update progress to `0.5f`. This should no-op as progress becomes read-only
+  // after being marked completed.
+  model().UpdateProgressForItem(item_ptr->id(), 0.5f);
+  EXPECT_FALSE(observation.TakeLastUpdatedItem());
+  EXPECT_EQ(item_ptr->progress(), 1.f);
+}
+
+}  // namespace ash
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index 4e62c863..6fbcd1b 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -10,6 +10,7 @@
 
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/search_box/search_box_view_delegate.h"
+#include "base/bind.h"
 #include "base/macros.h"
 #include "third_party/skia/include/core/SkPath.h"
 #include "ui/base/ime/text_input_flags.h"
@@ -95,6 +96,16 @@
     SetInkDropMode(InkDropMode::ON);
     // InkDropState will reset after clicking.
     SetHasInkDropActionOnClick(true);
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](InkDropHostView* host) {
+          constexpr SkColor ripple_color =
+              SkColorSetA(gfx::kGoogleGrey900, 0x12);
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()), ripple_color);
+          highlight->set_visible_opacity(1.f);
+          return highlight;
+        },
+        this));
 
     SetPreferredSize({kSearchBoxButtonSizeDip, kSearchBoxButtonSizeDip});
     SetImageHorizontalAlignment(ALIGN_CENTER);
@@ -139,15 +150,6 @@
         GetInkDropCenterBasedOnLastEvent(), ripple_color, 1.0f);
   }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    constexpr SkColor ripple_color = SkColorSetA(gfx::kGoogleGrey900, 0x12);
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(size()), ripple_color);
-    highlight->set_visible_opacity(1.f);
-    return highlight;
-  }
-
  private:
   int GetInkDropRadius() const { return width() / 2; }
 
diff --git a/ash/shell.cc b/ash/shell.cc
index 56e6206..3d41e51 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -188,7 +188,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "dbus/bus.h"
-#include "media/base/media_switches.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
 #include "ui/aura/layout_manager.h"
diff --git a/ash/system/message_center/stacked_notification_bar.cc b/ash/system/message_center/stacked_notification_bar.cc
index 7ef25fa..6216e3ab 100644
--- a/ash/system/message_center/stacked_notification_bar.cc
+++ b/ash/system/message_center/stacked_notification_bar.cc
@@ -12,6 +12,7 @@
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/unified/rounded_label_button.h"
+#include "base/bind.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/layer_animator.h"
@@ -49,6 +50,18 @@
     TrayPopupUtils::ConfigureTrayPopupButton(
         this, TrayPopupInkDropStyle::FILL_BOUNDS, /*highlight_on_hover=*/true,
         /*highlight_on_focus=*/true);
+    // SetCreateInkDropHighlightCallback is explicitly called after
+    // ConfigureTrayPopupButton as the former configures the InkDrop and this
+    // overrides that behavior.
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](InkDropHostView* host) {
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()), message_center_style::kInkRippleColor);
+          highlight->set_visible_opacity(
+              message_center_style::kInkRippleOpacity);
+          return highlight;
+        },
+        this));
   }
 
   ~StackingBarLabelButton() override = default;
@@ -86,14 +99,6 @@
         message_center_style::kInkRippleOpacity);
   }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(size()), message_center_style::kInkRippleColor);
-    highlight->set_visible_opacity(message_center_style::kInkRippleOpacity);
-    return highlight;
-  }
-
  private:
   UnifiedMessageCenterView* message_center_view_;
   DISALLOW_COPY_AND_ASSIGN(StackingBarLabelButton);
diff --git a/ash/system/toast/toast_overlay.cc b/ash/system/toast/toast_overlay.cc
index 533a9520..95c422d 100644
--- a/ash/system/toast/toast_overlay.cc
+++ b/ash/system/toast/toast_overlay.cc
@@ -15,6 +15,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/wm/work_area_insets.h"
+#include "base/bind.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -132,6 +133,13 @@
       : views::LabelButton(std::move(callback), text, CONTEXT_TOAST_OVERLAY) {
     SetInkDropMode(InkDropMode::ON);
     SetHasInkDropActionOnClick(true);
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](InkDropHostView* host) {
+          return std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->GetLocalBounds().size()),
+              host->GetInkDropBaseColor());
+        },
+        this));
 
     // Treat the space below the baseline as a margin.
     int vertical_spacing =
@@ -148,14 +156,6 @@
   ToastOverlayButton& operator=(const ToastOverlayButton&) = delete;
   ~ToastOverlayButton() override = default;
 
- protected:
-  // views::LabelButton:
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    return std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(GetLocalBounds().size()), GetInkDropBaseColor());
-  }
-
  private:
   friend class ToastOverlay;  // for ToastOverlay::ClickDismissButtonForTesting.
 
diff --git a/ash/system/tray/actionable_view.cc b/ash/system/tray/actionable_view.cc
index 50ea80e..3afe0cf 100644
--- a/ash/system/tray/actionable_view.cc
+++ b/ash/system/tray/actionable_view.cc
@@ -37,6 +37,8 @@
   SetCreateInkDropCallback(base::BindRepeating(
       [](InkDropHostView* host) { return TrayPopupUtils::CreateInkDrop(host); },
       this));
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      &TrayPopupUtils::CreateInkDropHighlight, base::Unretained(this)));
 }
 
 ActionableView::~ActionableView() {
@@ -74,11 +76,6 @@
   return TrayPopupUtils::CreateInkDropRipple(ink_drop_style_, this);
 }
 
-std::unique_ptr<views::InkDropHighlight>
-ActionableView::CreateInkDropHighlight() const {
-  return TrayPopupUtils::CreateInkDropHighlight(this);
-}
-
 void ActionableView::ButtonPressed(const ui::Event& event) {
   bool destroyed = false;
   destroyed_ = &destroyed;
diff --git a/ash/system/tray/actionable_view.h b/ash/system/tray/actionable_view.h
index d5122b52..5d7e75b4 100644
--- a/ash/system/tray/actionable_view.h
+++ b/ash/system/tray/actionable_view.h
@@ -49,8 +49,6 @@
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
 
  private:
   void ButtonPressed(const ui::Event& event);
diff --git a/ash/system/tray/tray_background_view.cc b/ash/system/tray/tray_background_view.cc
index f9b6f417..eb03adf 100644
--- a/ash/system/tray/tray_background_view.cc
+++ b/ash/system/tray/tray_background_view.cc
@@ -26,6 +26,7 @@
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_event_filter.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
+#include "base/bind.h"
 #include "base/scoped_multi_source_observation.h"
 #include "base/time/time.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -181,6 +182,28 @@
   SetInkDropVisibleOpacity(ripple_attributes.inkdrop_opacity);
 
   SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](TrayBackgroundView* host) {
+        gfx::Rect bounds = host->GetBackgroundBounds();
+        // Currently, we don't handle view resize. To compensate for that,
+        // enlarge the bounds by two tray icons so that the highlight looks good
+        // even if two more icons are added when it is visible. Note that ink
+        // drop mask handles resize correctly, so the extra highlight would be
+        // clipped.
+        // TODO(mohsen): Remove this extra size when resize is handled properly
+        // (see https://crbug.com/669253).
+        const int icon_size = kTrayIconSize + 2 * kTrayImageItemPadding;
+        bounds.set_width(bounds.width() + 2 * icon_size);
+        bounds.set_height(bounds.height() + 2 * icon_size);
+        const AshColorProvider::RippleAttributes ripple_attributes =
+            AshColorProvider::Get()->GetRippleAttributes();
+        auto highlight = std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(bounds.size()), ripple_attributes.base_color);
+        highlight->set_visible_opacity(ripple_attributes.highlight_opacity);
+        return highlight;
+      },
+      this));
+
   SetLayoutManager(std::make_unique<views::FillLayout>());
   SetInstallFocusRingOnFocus(true);
 
@@ -334,26 +357,6 @@
       ripple_attributes.base_color, ripple_attributes.inkdrop_opacity);
 }
 
-std::unique_ptr<views::InkDropHighlight>
-TrayBackgroundView::CreateInkDropHighlight() const {
-  gfx::Rect bounds = GetBackgroundBounds();
-  // Currently, we don't handle view resize. To compensate for that, enlarge the
-  // bounds by two tray icons so that the highlight looks good even if two more
-  // icons are added when it is visible. Note that ink drop mask handles resize
-  // correctly, so the extra highlight would be clipped.
-  // TODO(mohsen): Remove this extra size when resize is handled properly (see
-  // https://crbug.com/669253).
-  const int icon_size = kTrayIconSize + 2 * kTrayImageItemPadding;
-  bounds.set_width(bounds.width() + 2 * icon_size);
-  bounds.set_height(bounds.height() + 2 * icon_size);
-  const AshColorProvider::RippleAttributes ripple_attributes =
-      AshColorProvider::Get()->GetRippleAttributes();
-  auto highlight = std::make_unique<views::InkDropHighlight>(
-      gfx::SizeF(bounds.size()), ripple_attributes.base_color);
-  highlight->set_visible_opacity(ripple_attributes.highlight_opacity);
-  return highlight;
-}
-
 void TrayBackgroundView::OnThemeChanged() {
   ActionableView::OnThemeChanged();
   UpdateBackground();
diff --git a/ash/system/tray/tray_background_view.h b/ash/system/tray/tray_background_view.h
index f4081deb..049f3205 100644
--- a/ash/system/tray/tray_background_view.h
+++ b/ash/system/tray/tray_background_view.h
@@ -47,8 +47,6 @@
 
   // ActionableView:
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
   void OnThemeChanged() override;
 
   // VirtualKeyboardModel::Observer:
diff --git a/ash/system/unified/page_indicator_view.cc b/ash/system/unified/page_indicator_view.cc
index e307bb8..a3e8f364 100644
--- a/ash/system/unified/page_indicator_view.cc
+++ b/ash/system/unified/page_indicator_view.cc
@@ -60,6 +60,14 @@
                                                /*highlight_on_hover=*/true);
         },
         this));
+    SetCreateInkDropHighlightCallback(base::BindRepeating(
+        [](PageIndicatorButton* host) {
+          auto highlight = std::make_unique<views::InkDropHighlight>(
+              gfx::SizeF(host->size()), host->ripple_base_color_);
+          highlight->set_visible_opacity(host->highlight_opacity_);
+          return highlight;
+        },
+        this));
   }
 
   ~PageIndicatorButton() override {}
@@ -123,14 +131,6 @@
         inkdrop_opacity_);
   }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    auto highlight = std::make_unique<views::InkDropHighlight>(
-        gfx::SizeF(size()), ripple_base_color_);
-    highlight->set_visible_opacity(highlight_opacity_);
-    return highlight;
-  }
-
   void NotifyClick(const ui::Event& event) override {
     Button::NotifyClick(event);
     GetInkDrop()->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
diff --git a/ash/wm/desks/close_desk_button.cc b/ash/wm/desks/close_desk_button.cc
index 35514ea..a4c6adcc 100644
--- a/ash/wm/desks/close_desk_button.cc
+++ b/ash/wm/desks/close_desk_button.cc
@@ -8,6 +8,7 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/ash_color_provider.h"
+#include "base/bind.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
@@ -30,6 +31,15 @@
   SetInkDropMode(InkDropMode::ON);
   SetHasInkDropActionOnClick(true);
   views::InkDrop::UseInkDropForFloodFillRipple(this);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](CloseDeskButton* host) {
+        auto highlight = std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(host->size()), host->GetInkDropBaseColor());
+        highlight->set_visible_opacity(host->highlight_opacity_);
+        return highlight;
+      },
+      this));
+
   SetFocusPainter(nullptr);
   SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
 
@@ -44,14 +54,6 @@
   return "CloseDeskButton";
 }
 
-std::unique_ptr<views::InkDropHighlight>
-CloseDeskButton::CreateInkDropHighlight() const {
-  auto highlight = std::make_unique<views::InkDropHighlight>(
-      gfx::SizeF(size()), GetInkDropBaseColor());
-  highlight->set_visible_opacity(highlight_opacity_);
-  return highlight;
-}
-
 SkColor CloseDeskButton::GetInkDropBaseColor() const {
   return inkdrop_base_color_;
 }
diff --git a/ash/wm/desks/close_desk_button.h b/ash/wm/desks/close_desk_button.h
index 72d2c36d..e30bae2 100644
--- a/ash/wm/desks/close_desk_button.h
+++ b/ash/wm/desks/close_desk_button.h
@@ -28,8 +28,6 @@
 
   // views::ImageButton:
   const char* GetClassName() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
   SkColor GetInkDropBaseColor() const override;
   void OnThemeChanged() override;
 
diff --git a/ash/wm/desks/zero_state_button.cc b/ash/wm/desks/zero_state_button.cc
index 0748ade..7782b4a 100644
--- a/ash/wm/desks/zero_state_button.cc
+++ b/ash/wm/desks/zero_state_button.cc
@@ -59,6 +59,18 @@
   views::InkDrop::UseInkDropForFloodFillRipple(this,
                                                /*highlight_on_hover=*/false,
                                                /*highlight_on_focus=*/false);
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](DeskButtonBase* host) {
+        auto highlight = std::make_unique<views::InkDropHighlight>(
+            gfx::SizeF(host->size()), host->GetInkDropBaseColor());
+        highlight->set_visible_opacity(
+            AshColorProvider::Get()
+                ->GetRippleAttributes(host->background_color_)
+                .highlight_opacity);
+        return highlight;
+      },
+      this));
+
   SetInkDropMode(InkDropMode::ON);
   SetHasInkDropActionOnClick(true);
   SetFocusPainter(nullptr);
@@ -95,16 +107,6 @@
   }
 }
 
-std::unique_ptr<views::InkDropHighlight>
-DeskButtonBase::CreateInkDropHighlight() const {
-  auto highlight = std::make_unique<views::InkDropHighlight>(
-      gfx::SizeF(size()), GetInkDropBaseColor());
-  highlight->set_visible_opacity(AshColorProvider::Get()
-                                     ->GetRippleAttributes(background_color_)
-                                     .highlight_opacity);
-  return highlight;
-}
-
 SkColor DeskButtonBase::GetInkDropBaseColor() const {
   return AshColorProvider::Get()
       ->GetRippleAttributes(background_color_)
diff --git a/ash/wm/desks/zero_state_button.h b/ash/wm/desks/zero_state_button.h
index 633efb09..dbbd54da 100644
--- a/ash/wm/desks/zero_state_button.h
+++ b/ash/wm/desks/zero_state_button.h
@@ -28,8 +28,6 @@
   // LabelButton:
   const char* GetClassName() const override;
   void OnPaintBackground(gfx::Canvas* canvas) override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
   SkColor GetInkDropBaseColor() const override;
   void OnThemeChanged() override;
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 3deef35..bb30c51 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3921,6 +3921,10 @@
       "android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
       "android/java/src/org/chromium/base/compat/ApiHelperForR.java",
       "android/java/src/org/chromium/base/compat/ApiHelperForS.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankFrameMetricsListener.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankMetricMeasurement.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
       "android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
       "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
       "android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
@@ -4198,6 +4202,9 @@
       "android/junit/src/org/chromium/base/PromiseTest.java",
       "android/junit/src/org/chromium/base/UnownedUserDataHostTest.java",
       "android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/JankActivityTrackerTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/JankFrameMetricsListenerTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/JankMetricMeasurementTest.java",
       "android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java",
       "android/junit/src/org/chromium/base/metrics/CachingUmaRecorderTest.java",
       "android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java",
diff --git a/base/allocator/partition_allocator/address_pool_manager_bitmap.h b/base/allocator/partition_allocator/address_pool_manager_bitmap.h
index eb4551b..bbe50c89 100644
--- a/base/allocator/partition_allocator/address_pool_manager_bitmap.h
+++ b/base/allocator/partition_allocator/address_pool_manager_bitmap.h
@@ -60,21 +60,30 @@
   // Returns false for nullptr.
   static bool IsManagedByNonBRPPool(const void* address) {
     uintptr_t address_as_uintptr = reinterpret_cast<uintptr_t>(address);
+    static_assert(
+        std::numeric_limits<uintptr_t>::max() / PageAllocationGranularity() <
+            non_brp_pool_bits_.size(),
+        "The bitmap is too small, will result in unchecked out of bounds "
+        "accesses.");
     // It is safe to read |non_brp_pool_bits_| without a lock since the caller
     // is responsible for guaranteeing that the address is inside a valid
     // allocation and the deallocation call won't race with this call.
-    return TS_UNCHECKED_READ(non_brp_pool_bits_)
-        .test(address_as_uintptr / PageAllocationGranularity());
+    return TS_UNCHECKED_READ(
+        non_brp_pool_bits_)[address_as_uintptr / PageAllocationGranularity()];
   }
 
   // Returns false for nullptr.
   static bool IsManagedByBRPPool(const void* address) {
     uintptr_t address_as_uintptr = reinterpret_cast<uintptr_t>(address);
+    static_assert(std::numeric_limits<uintptr_t>::max() >>
+                      kBitShiftOfBRPPoolBitmap < brp_pool_bits_.size(),
+                  "The bitmap is too small, will result in unchecked out of "
+                  "bounds accesses.");
     // It is safe to read |brp_pool_bits_| without a lock since the caller
     // is responsible for guaranteeing that the address is inside a valid
     // allocation and the deallocation call won't race with this call.
-    return TS_UNCHECKED_READ(brp_pool_bits_)
-        .test(address_as_uintptr >> kBitShiftOfBRPPoolBitmap);
+    return TS_UNCHECKED_READ(
+        brp_pool_bits_)[address_as_uintptr >> kBitShiftOfBRPPoolBitmap];
   }
 
 #if BUILDFLAG(USE_BRP_POOL_BLOCKLIST)
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java b/base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java
new file mode 100644
index 0000000..e08735f5
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java
@@ -0,0 +1,154 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import android.app.Activity;
+import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ActivityStateListener;
+import org.chromium.base.ThreadUtils.ThreadChecker;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class takes an activity, listens and records FrameMetrics in a JankMetricMeasurement
+ * instance. A new HandlerThread is started to listen to FrameMetric events.
+ * In addition it starts/stops recording based on activity lifecycle.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+class JankActivityTracker implements ActivityStateListener {
+    private static final long METRIC_DELAY_MS = 30_000;
+
+    static JankActivityTracker create(Activity context) {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+        JankFrameMetricsListener listener = new JankFrameMetricsListener(measurement);
+        return new JankActivityTracker(context, listener, measurement);
+    }
+
+    private final JankFrameMetricsListener mFrameMetricsListener;
+    private final JankMetricMeasurement mMeasurement;
+    private final AtomicBoolean mIsMetricReporterLooping = new AtomicBoolean(false);
+    private final ThreadChecker mThreadChecker = new ThreadChecker();
+
+    private final Runnable mMetricReporter = new Runnable() {
+        @Override
+        public void run() {
+            // TODO(salg): Log frame durations and jank bursts.
+            mMeasurement.clear();
+
+            if (mIsMetricReporterLooping.get()) {
+                getOrCreateHandler().postDelayed(mMetricReporter, METRIC_DELAY_MS);
+            }
+        }
+    };
+
+    @Nullable
+    protected HandlerThread mHandlerThread;
+    @Nullable
+    private Handler mHandler;
+    private WeakReference<Activity> mActivityReference;
+
+    JankActivityTracker(Activity context, JankFrameMetricsListener listener,
+            JankMetricMeasurement measurement) {
+        mActivityReference = new WeakReference<>(context);
+        mFrameMetricsListener = listener;
+        mMeasurement = measurement;
+    }
+
+    void initialize() {
+        mThreadChecker.assertOnValidThreadAndState();
+        Activity activity = mActivityReference.get();
+        if (activity != null) {
+            ApplicationStatus.registerStateListenerForActivity(this, activity);
+            @ActivityState
+            int activityState = ApplicationStatus.getStateForActivity(activity);
+            onActivityStateChange(activity, activityState);
+            activity.getWindow().addOnFrameMetricsAvailableListener(
+                    mFrameMetricsListener, getOrCreateHandler());
+        }
+    }
+
+    void destroy() {
+        mThreadChecker.assertOnValidThreadAndState();
+        ApplicationStatus.unregisterActivityStateListener(this);
+        stopMetricRecording();
+        stopReportingTimer();
+        Activity activity = mActivityReference.get();
+        if (activity != null) {
+            activity.getWindow().removeOnFrameMetricsAvailableListener(mFrameMetricsListener);
+        }
+        mThreadChecker.destroy();
+    }
+
+    protected Handler getOrCreateHandler() {
+        if (mHandler == null) {
+            mHandlerThread = new HandlerThread("Jank-Tracker");
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+        return mHandler;
+    }
+
+    private void startReportingTimer() {
+        mThreadChecker.assertOnValidThreadAndState();
+        // If mIsMetricReporterLooping was already true then there's no need to post another task.
+        if (mIsMetricReporterLooping.getAndSet(true)) {
+            return;
+        }
+        getOrCreateHandler().postDelayed(mMetricReporter, METRIC_DELAY_MS);
+    }
+
+    private void stopReportingTimer() {
+        mThreadChecker.assertOnValidThreadAndState();
+        if (!mIsMetricReporterLooping.get()) {
+            return;
+        }
+        // Remove any existing mMetricReporter delayed tasks.
+        getOrCreateHandler().removeCallbacks(mMetricReporter);
+        // Disable mMetricReporter looping.
+        mIsMetricReporterLooping.set(false);
+        // Run mMetricReporter one last time immediately.
+        getOrCreateHandler().post(mMetricReporter);
+    }
+
+    private void startMetricRecording() {
+        mThreadChecker.assertOnValidThreadAndState();
+        mFrameMetricsListener.setIsListenerRecording(true);
+    }
+
+    private void stopMetricRecording() {
+        mThreadChecker.assertOnValidThreadAndState();
+        mFrameMetricsListener.setIsListenerRecording(false);
+    }
+
+    @Override
+    public void onActivityStateChange(Activity activity, @ActivityState int newState) {
+        switch (newState) {
+            case ActivityState.STARTED: // Intentional fallthrough.
+            case ActivityState.RESUMED:
+                startReportingTimer();
+                startMetricRecording();
+                break;
+            case ActivityState.PAUSED:
+                // This method can be called at any moment safely, we want to report metrics even
+                // when the activity is paused.
+                startReportingTimer();
+                stopMetricRecording();
+                break;
+            case ActivityState.STOPPED:
+                stopMetricRecording();
+                stopReportingTimer();
+                break;
+        }
+    }
+}
\ No newline at end of file
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankFrameMetricsListener.java b/base/android/java/src/org/chromium/base/jank_tracker/JankFrameMetricsListener.java
new file mode 100644
index 0000000..579b9e14
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankFrameMetricsListener.java
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import android.os.Build.VERSION_CODES;
+import android.view.FrameMetrics;
+import android.view.Window;
+import android.view.Window.OnFrameMetricsAvailableListener;
+
+import androidx.annotation.RequiresApi;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class receives FrameMetrics callbacks and records frame durations in a JankMetricMeasurement
+ * instance. Recording can be toggled from any thread, but the actual recording occurs in the thread
+ * specified when this is attached to a window with addOnFrameMetricsAvailableListener(). This class
+ * only adds data to the provided JankMetricMeasurement instance, its owner must clear it to avoid
+ * OOMs.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+class JankFrameMetricsListener implements OnFrameMetricsAvailableListener {
+    private final JankMetricMeasurement mMeasurement;
+    private final AtomicBoolean mIsRecording;
+
+    JankFrameMetricsListener(JankMetricMeasurement measurement) {
+        mMeasurement = measurement;
+        mIsRecording = new AtomicBoolean(false);
+    }
+
+    /**
+     * Toggles recording into JankMetricMeasurement, can be invoked from any thread.
+     * @param isRecording
+     */
+    public void setIsListenerRecording(boolean isRecording) {
+        mIsRecording.set(isRecording);
+    }
+
+    @RequiresApi(api = VERSION_CODES.N)
+    @Override
+    public void onFrameMetricsAvailable(
+            Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
+        if (!mIsRecording.get()) {
+            return;
+        }
+
+        long frameTotalDurationNs = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
+
+        long timestampNs = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
+
+        mMeasurement.addFrameMeasurement(
+                timestampNs, frameTotalDurationNs, dropCountSinceLastInvocation);
+    }
+}
\ No newline at end of file
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankMetricMeasurement.java b/base/android/java/src/org/chromium/base/jank_tracker/JankMetricMeasurement.java
new file mode 100644
index 0000000..2a49cbf9
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankMetricMeasurement.java
@@ -0,0 +1,242 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import java.util.ArrayList;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * This class records frame durations and timestamps. And it calculates a jank burst metric.
+ *
+ * Jank bursts are periods with janky frames in a quick succession. A non-janky frame may be
+ * included in this measurement if it's preceded and succeeded by janky frames, as long as the three
+ * frames were drawn in a quick succession (called 'consecutive' in this file). Jank bursts are
+ * delimited by either a succession (2 or more) of non-janky frames or a period of inactivity
+ * (defined by JANK_BURST_CONSECUTIVE_FRAME_THRESHOLD_MS).
+ *
+ * Example: X = Janky frame. O = Non-janky frame.
+ *
+ *  O
+ *  O
+ *  X <- Jank burst 1 starts here.
+ *  X
+ *  O <- This frame is not janky, but as the adjacent frames are janky we include it.
+ *  X <- Jank burst 1 ends here.
+ *  O
+ *  O
+ *  X <- Jank burst 2 starts here.
+ *  X <- Jank burst 2 ends here.
+ *  500 ms later...
+ *  X <- Jank burst 3 starts here. Because some time passed between the last frame and this one
+ *       we assume their jankyness was related to different events/animations.
+ *  X <- Jank burst 3 ends here.
+ *  O
+ *  O
+ *
+ *  In this example the jank burst metric would report 3 values, which are the sum of the frame
+ *  duration of all frames inside a jank burst, including the first and last frames.
+ */
+class JankMetricMeasurement {
+    protected static class JankMetric {
+        private final Long[] mTimestampsNs;
+        private final Long[] mDurationsNs;
+        private final Long[] mJankBurstsNs;
+        private final int mSkippedFrames;
+
+        public JankMetric(
+                Long[] timestampsNs, Long[] durationsNs, Long[] jankBurstsNs, int skippedFrames) {
+            mTimestampsNs = timestampsNs;
+            mDurationsNs = durationsNs;
+            mJankBurstsNs = jankBurstsNs;
+            mSkippedFrames = skippedFrames;
+        }
+
+        public Long[] getTimestamps() {
+            return mTimestampsNs;
+        }
+
+        public Long[] getDurations() {
+            return mDurationsNs;
+        }
+
+        public Long[] getJankBursts() {
+            return mJankBurstsNs;
+        }
+
+        public int getSkippedFrames() {
+            return mSkippedFrames;
+        }
+    }
+
+    // Guards access to the fields below.
+    private final Object mLock = new Object();
+
+    // Array of timestamps stored in nanoseconds, they represent the moment when each frame
+    // finished drawing, must always be the same size as mTotalDurationsNs.
+    @GuardedBy("mLock")
+    private final ArrayList<Long> mTimestampsNs;
+
+    // Array of total durations stored in nanoseconds, they represent how long each frame took to
+    // draw, must always be the same size as mTimestampsNs.
+    @GuardedBy("mLock")
+    private final ArrayList<Long> mTotalDurationsNs;
+
+    // Number of frames that FrameMetrics was unable to report on, due to excessive activity on its
+    // handler thread.
+    @GuardedBy("mLock")
+    private int mSkippedFrames;
+
+    private static final long NANOSECONDS_PER_MILLISECOND = 1_000_000;
+
+    // Threshold in milliseconds to distinguish janky and non-janky frames. Any frames whose
+    // duration is greater than this number are considered janky. Ideally this number would depend
+    // on the device's refresh rate, but for now we assume the device runs at 60hz.
+    private static final long JANK_THRESHOLD_NS = 16 * NANOSECONDS_PER_MILLISECOND;
+
+    // Maximum duration between frames to consider them consecutive. This is used to split jank
+    // bursts when 2 janky frames are separated by an idle period.
+    private static final long JANK_BURST_CONSECUTIVE_FRAME_THRESHOLD_NS =
+            50 * NANOSECONDS_PER_MILLISECOND;
+
+    JankMetricMeasurement() {
+        mTimestampsNs = new ArrayList<>();
+        mTotalDurationsNs = new ArrayList<>();
+        mSkippedFrames = 0;
+    }
+
+    /**
+     *  Records a timestamp and total draw duration for a single frame, both values are in
+     * milliseconds.
+     */
+    void addFrameMeasurement(long timestampNs, long totalDurationNs, int skippedFrames) {
+        synchronized (mLock) {
+            mTimestampsNs.add(timestampNs);
+            mTotalDurationsNs.add(totalDurationNs);
+            mSkippedFrames += skippedFrames;
+        }
+    }
+
+    /**
+     *  Returns an array of all recorded jank bursts measured in nanoseconds (see class doc for
+     * details).
+     */
+    private static Long[] calculateJankBurstDurationsNs(Long[] timestampsNs, Long[] durationsNs) {
+        assert timestampsNs.length == durationsNs.length;
+        ArrayList<Long> jankBurstDurationsNs = new ArrayList<>();
+        long currentJankBurstDurationNs = 0;
+        for (int i = 0; i < timestampsNs.length; i++) {
+            // If there's an existing jank burst, but the current frame is not consecutive
+            // compared to the last then we end the burst.
+            if (i > 0 && currentJankBurstDurationNs > 0
+                    && !areFramesConsecutive(i - 1, i, timestampsNs, durationsNs)) {
+                jankBurstDurationsNs.add(currentJankBurstDurationNs);
+                currentJankBurstDurationNs = 0;
+            }
+
+            long currentFrameDurationNs = durationsNs[i];
+
+            // If the frame is janky we start or continue the jank burst.
+            if (isFrameJanky(i, durationsNs)) {
+                currentJankBurstDurationNs += currentFrameDurationNs;
+            } else {
+                if (currentJankBurstDurationNs > 0) {
+                    // If the frame is not janky, but there's a jank burst and the adjacent
+                    // frames are consecutive and janky then we count this frame's duration in
+                    // the burst.
+                    if (areFramesConsecutive(i - 1, i, timestampsNs, durationsNs)
+                            && areFramesConsecutive(i, i + 1, timestampsNs, durationsNs)
+                            && isFrameJanky(i + 1, durationsNs)) {
+                        currentJankBurstDurationNs += currentFrameDurationNs;
+                    } else {
+                        // If the frame is not janky and the next frame is not janky or consecutive
+                        // then we end the burst.
+                        jankBurstDurationsNs.add(currentJankBurstDurationNs);
+                        currentJankBurstDurationNs = 0;
+                    }
+                }
+            }
+        }
+
+        // TODO(salg): Jank bursts may spread across measurements, consider removing this and
+        // continuing the jank burst after we record more frames.
+        if (currentJankBurstDurationNs > 0) {
+            jankBurstDurationsNs.add(currentJankBurstDurationNs);
+        }
+
+        return jankBurstDurationsNs.toArray(new Long[jankBurstDurationsNs.size()]);
+    }
+
+    /**
+     *  Returns a new object with metrics for all frames recorded since started or clear() was
+     * called.
+     */
+    JankMetric getMetrics() {
+        Long[] timestampsNs;
+        Long[] totalDurationsNs;
+        int skippedFrames;
+
+        synchronized (mLock) {
+            timestampsNs = mTimestampsNs.toArray(new Long[mTimestampsNs.size()]);
+            totalDurationsNs = mTotalDurationsNs.toArray(new Long[mTotalDurationsNs.size()]);
+            skippedFrames = mSkippedFrames;
+        }
+
+        Long[] jankBursts = calculateJankBurstDurationsNs(timestampsNs, totalDurationsNs);
+
+        return new JankMetric(timestampsNs, totalDurationsNs, jankBursts, skippedFrames);
+    }
+
+    /**
+     * Clears this measurement.
+     */
+    void clear() {
+        synchronized (mLock) {
+            mTimestampsNs.clear();
+            mTotalDurationsNs.clear();
+            mSkippedFrames = 0;
+        }
+    }
+
+    /**
+     * Checks if the frame at frameIndex is janky (i.e. Its duration is greater than 16 ms). Returns
+     * false if frameIndex is out of bounds.
+     */
+    private static boolean isFrameJanky(int frameIndex, Long[] durationsNs) {
+        if (frameIndex < 0 || frameIndex >= durationsNs.length) return false;
+
+        long frameDurationNs = durationsNs[frameIndex];
+
+        return frameDurationNs > JANK_THRESHOLD_NS;
+    }
+
+    /**
+     * Checks if the frame at secondFrameIndex was drawn immediately after the frame at
+     * firstFrameIndex (with a margin of 50ms defined in JANK_BURST_CONSECUTIVE_FRAME_THRESHOLD_MS).
+     * Timestamps are recorded at the end of each frame, so we compare the end of the first frame
+     * and the beginning of the second frame. If either index is out of bounds then we return false.
+     */
+    private static boolean areFramesConsecutive(
+            int firstFrameIndex, int secondFrameIndex, Long[] timestampsNs, Long[] durationsNs) {
+        assert firstFrameIndex < secondFrameIndex;
+        assert timestampsNs.length == durationsNs.length;
+        if (firstFrameIndex < 0 || secondFrameIndex < 0) {
+            return false;
+        }
+        if (firstFrameIndex >= timestampsNs.length || secondFrameIndex >= timestampsNs.length) {
+            return false;
+        }
+
+        long firstFrameEndNs = timestampsNs[firstFrameIndex];
+
+        long secondFrameEndNs = timestampsNs[secondFrameIndex];
+        long secondFrameDurationNs = durationsNs[secondFrameIndex];
+        long secondFrameStartNs = secondFrameEndNs - secondFrameDurationNs;
+
+        long timeBetweenFramesNs = secondFrameStartNs - firstFrameEndNs;
+
+        return (timeBetweenFramesNs < JANK_BURST_CONSECUTIVE_FRAME_THRESHOLD_NS);
+    }
+}
\ No newline at end of file
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java b/base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java
new file mode 100644
index 0000000..b8284c16
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java
@@ -0,0 +1,38 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import android.app.Activity;
+import android.os.Build.VERSION_CODES;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Class for recording janky frame metrics for a specific Activity.
+ *
+ * It should be constructed when the activity is created, recording starts and stops automatically
+ * based on activity state. When the activity is being destroyed {@link #destroy()} should be called
+ * to clear the activity state observer. All methods should be called from the UI thread.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+public final class JankTracker {
+    private final JankActivityTracker mActivityTracker;
+
+    /**
+     * Creates a new JankTracker instance tracking UI rendering of an activity. Metric recording
+     * starts when the activity starts, and it's paused when the activity stops.
+     */
+    public JankTracker(Activity activity) {
+        mActivityTracker = JankActivityTracker.create(activity);
+        mActivityTracker.initialize();
+    }
+
+    /**
+     * Stops listening for Activity state changes.
+     */
+    public void destroy() {
+        mActivityTracker.destroy();
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/JankActivityTrackerTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/JankActivityTrackerTest.java
new file mode 100644
index 0000000..927f2bd
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/JankActivityTrackerTest.java
@@ -0,0 +1,171 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.view.Window;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ *  Tests for JankActivityTracker.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class JankActivityTrackerTest {
+    ShadowLooper mShadowLooper;
+
+    @Mock
+    private Activity mActivity;
+
+    @Mock
+    private Window mWindow;
+
+    @Mock
+    private JankFrameMetricsListener mJankFrameMetricsListener;
+
+    @Mock
+    private JankMetricMeasurement mJankMetricMeasurement;
+
+    JankActivityTracker createJankActivityTracker(Activity activity) {
+        JankActivityTracker tracker = new JankActivityTracker(
+                activity, mJankFrameMetricsListener, mJankMetricMeasurement);
+        mShadowLooper = Shadow.extract(tracker.getOrCreateHandler().getLooper());
+
+        return tracker;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mActivity.getWindow()).thenReturn(mWindow);
+
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.CREATED);
+    }
+
+    @Test
+    public void jankTrackerTest_TestInitialize() {
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        // Verify that we are listening to frame metrics.
+        // Initialize also starts listening to activity lifecycle events, but that's harder to
+        // verify.
+        verify(mWindow).addOnFrameMetricsAvailableListener(any(), any());
+    }
+
+    @Test
+    public void jankTrackerTest_TestActivityResume() {
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.RESUMED);
+
+        // When an activity resumes we schedule a repeating task to report metrics on the tracker's
+        // handler thread.
+        mShadowLooper.runOneTask();
+
+        // The reporting task should clear the jank measurement.
+        verify(mJankMetricMeasurement).clear();
+
+        // The reporting task should be looping, so another task should be posted.
+        Assert.assertTrue(mShadowLooper.getScheduler().areAnyRunnable());
+
+        // When an activity resumes we start recording metrics.
+        verify(mJankFrameMetricsListener, atLeastOnce()).setIsListenerRecording(true);
+    }
+
+    @Test
+    public void jankTrackerTest_TestActivityPause() {
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.RESUMED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.PAUSED);
+
+        mShadowLooper.runOneTask();
+
+        // When an activity pauses the reporting task should still be looping.
+        Assert.assertTrue(mShadowLooper.getScheduler().areAnyRunnable());
+
+        InOrder orderVerifier = Mockito.inOrder(mJankFrameMetricsListener);
+
+        orderVerifier.verify(mJankFrameMetricsListener, atLeastOnce()).setIsListenerRecording(true);
+        // When an activity pauses we stop recording metrics.
+        orderVerifier.verify(mJankFrameMetricsListener).setIsListenerRecording(false);
+    }
+
+    @Test
+    public void jankTrackerTest_TestActivityStop() {
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.RESUMED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.PAUSED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STOPPED);
+
+        // When an activity stops we run the reporting task one last time and stop it.
+        mShadowLooper.runOneTask();
+
+        Assert.assertFalse(mShadowLooper.getScheduler().areAnyRunnable());
+    }
+
+    @Test
+    public void jankTrackerTest_TestAttachTrackerOnResumedActivity() {
+        // Modify the activity's state before attaching JankActivityTracker.
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STARTED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.RESUMED);
+
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        // Verify that JankActivityTracker is running as expected for the Resumed state.
+        // Reporting task should be running and looping.
+        mShadowLooper.runOneTask();
+        verify(mJankMetricMeasurement).clear();
+        Assert.assertTrue(mShadowLooper.getScheduler().areAnyRunnable());
+        // Metric recording should be enabled.
+        verify(mJankFrameMetricsListener).setIsListenerRecording(true);
+    }
+
+    @Test
+    public void jankTrackerTest_TestOutOfOrderStateChange() {
+        JankActivityTracker jankActivityTracker = createJankActivityTracker(mActivity);
+        jankActivityTracker.initialize();
+
+        // Move the activity from STOPPED to RESUMED without calling STARTED.
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STOPPED);
+        ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.RESUMED);
+
+        // Verify that JankActivityTracker is running as expected for the Resumed state.
+        // Reporting task should be running and looping.
+        mShadowLooper.runOneTask();
+        verify(mJankMetricMeasurement).clear();
+        Assert.assertTrue(mShadowLooper.getScheduler().areAnyRunnable());
+        // Metric recording should be enabled.
+        verify(mJankFrameMetricsListener).setIsListenerRecording(true);
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/JankFrameMetricsListenerTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/JankFrameMetricsListenerTest.java
new file mode 100644
index 0000000..f2358870
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/JankFrameMetricsListenerTest.java
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.view.FrameMetrics;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ *  Tests for JankFrameMetricsListener.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class JankFrameMetricsListenerTest {
+    @Test
+    public void testMetricRecording_OffByDefault() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+        JankFrameMetricsListener metricsListener = new JankFrameMetricsListener(measurement);
+        FrameMetrics frameMetrics = mock(FrameMetrics.class);
+
+        when(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)).thenReturn(10_000_000L);
+
+        metricsListener.onFrameMetricsAvailable(null, frameMetrics, 0);
+
+        // By default metrics shouldn't be logged.
+        Assert.assertEquals(0, measurement.getMetrics().getDurations().length);
+        verifyZeroInteractions(frameMetrics);
+    }
+
+    @Test
+    public void testMetricRecording_EnableRecording() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+        JankFrameMetricsListener metricsListener = new JankFrameMetricsListener(measurement);
+        FrameMetrics frameMetrics = mock(FrameMetrics.class);
+
+        when(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)).thenReturn(10_000_000L);
+
+        metricsListener.setIsListenerRecording(true);
+        metricsListener.onFrameMetricsAvailable(null, frameMetrics, 0);
+
+        Assert.assertArrayEquals(new Long[] {10_000_000L}, measurement.getMetrics().getDurations());
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricMeasurementTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricMeasurementTest.java
new file mode 100644
index 0000000..cf7df97d
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricMeasurementTest.java
@@ -0,0 +1,116 @@
+// Copyright 2021 The Chromium 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.base.jank_tracker;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ *  Tests for JankMetricMeasurement.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class JankMetricMeasurementTest {
+    @Test
+    public void addFrameMeasurementTest() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_020_000_000L, 12_000_000L, 0);
+        measurement.addFrameMeasurement(1_040_000_000L, 20_000_000L, 0);
+        measurement.addFrameMeasurement(1_060_000_000L, 8_000_000L, 0);
+
+        assertArrayEquals(new Long[] {10_000_000L, 12_000_000L, 20_000_000L, 8_000_000L},
+                measurement.getMetrics().getDurations());
+    }
+
+    @Test
+    public void clearTest() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 10_000_000L, 0);
+
+        measurement.clear();
+
+        assertEquals(0, measurement.getMetrics().getDurations().length);
+    }
+
+    @Test
+    public void getJankBurstDurationsTest() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 15_000_000L, 0);
+        measurement.addFrameMeasurement(1_020_000_000L, 15_000_000L, 0);
+        measurement.addFrameMeasurement(1_040_000_000L, 50_000_000L, 0); // Burst starts here.
+        measurement.addFrameMeasurement(1_060_000_000L, 30_000_000L, 0);
+        measurement.addFrameMeasurement(1_080_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_100_000_000L, 30_000_000L, 0); // Burst ends here.
+        measurement.addFrameMeasurement(1_120_000_000L, 10_000_000L, 0);
+
+        assertArrayEquals(new Long[] {120_000_000L}, measurement.getMetrics().getJankBursts());
+    }
+
+    @Test
+    public void getJankBurstDurationsTest_TwoBursts() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 50_000_000L, 0); // Burst starts here.
+        measurement.addFrameMeasurement(1_020_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_040_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_060_000_000L, 50_000_000L, 0); // Burst ends here.
+        measurement.addFrameMeasurement(1_080_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_100_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_120_000_000L, 50_000_000L, 0); // Burst starts here.
+        measurement.addFrameMeasurement(1_140_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_160_000_000L, 50_000_000L, 0); // Burst ends here.
+
+        assertArrayEquals(
+                new Long[] {160_000_000L, 150_000_000L}, measurement.getMetrics().getJankBursts());
+    }
+
+    @Test
+    public void getJankBurstDurationsTest_OneLongBurst() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 50_000_000L, 0); // Burst starts here.
+        measurement.addFrameMeasurement(1_020_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_040_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_060_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_080_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_100_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_120_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_140_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(1_160_000_000L, 50_000_000L, 0); // Burst ends here.
+
+        assertArrayEquals(new Long[] {290_000_000L}, measurement.getMetrics().getJankBursts());
+    }
+
+    @Test
+    public void getJankBurstDurationsTest_ThreeBursts_WithNonConsecutiveFrames() {
+        JankMetricMeasurement measurement = new JankMetricMeasurement();
+
+        measurement.addFrameMeasurement(1_000_000_000L, 50_000_000L, 0); // Burst starts here.
+        measurement.addFrameMeasurement(1_020_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(1_040_000_000L, 50_000_000L, 0); // Burst ends here.
+        measurement.addFrameMeasurement(
+                2_000_000_000L, 50_000_000L, 0); // Burst starts here (~1000ms passed).
+        measurement.addFrameMeasurement(2_020_000_000L, 50_000_000L, 0);
+        measurement.addFrameMeasurement(2_040_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(2_060_000_000L, 50_000_000L, 0); // Burst ends here.
+        measurement.addFrameMeasurement(3_000_000_000L, 10_000_000L, 0);
+        measurement.addFrameMeasurement(
+                3_020_000_000L, 50_000_000L, 0); // Burst starts and ends here.
+        measurement.addFrameMeasurement(3_040_000_000L, 10_000_000L, 0);
+
+        assertArrayEquals(new Long[] {150_000_000L, 160_000_000L, 50_000_000L},
+                measurement.getMetrics().getJankBursts());
+    }
+}
diff --git a/base/task/single_thread_task_executor.cc b/base/task/single_thread_task_executor.cc
index fff5f83..e8aa7093 100644
--- a/base/task/single_thread_task_executor.cc
+++ b/base/task/single_thread_task_executor.cc
@@ -38,8 +38,8 @@
 
 SingleThreadTaskExecutor::~SingleThreadTaskExecutor() = default;
 
-scoped_refptr<SingleThreadTaskRunner> SingleThreadTaskExecutor::task_runner()
-    const {
+const scoped_refptr<SingleThreadTaskRunner>&
+SingleThreadTaskExecutor::task_runner() const {
   return default_task_queue_->task_runner();
 }
 
diff --git a/base/task/single_thread_task_executor.h b/base/task/single_thread_task_executor.h
index 03b47c7..3185e7a 100644
--- a/base/task/single_thread_task_executor.h
+++ b/base/task/single_thread_task_executor.h
@@ -40,7 +40,7 @@
   // if called.
   ~SingleThreadTaskExecutor();
 
-  scoped_refptr<SingleThreadTaskRunner> task_runner() const;
+  const scoped_refptr<SingleThreadTaskRunner>& task_runner() const;
 
   MessagePumpType type() const { return type_; }
 
diff --git a/base/task/single_thread_task_executor_unittest.cc b/base/task/single_thread_task_executor_unittest.cc
index 0e37d36..787ea25d 100644
--- a/base/task/single_thread_task_executor_unittest.cc
+++ b/base/task/single_thread_task_executor_unittest.cc
@@ -387,29 +387,41 @@
 
 #endif  // defined(OS_WIN)
 
-void PostNTasksThenQuit(TimeTicks begin_ticks,
-                        TimeTicks last_post_ticks,
-                        int posts_remaining,
-                        OnceClosure on_done) {
+void Post128KTasksThenQuit(SingleThreadTaskRunner* executor_task_runner,
+                           TimeTicks begin_ticks,
+                           TimeTicks last_post_ticks,
+                           TimeDelta slowest_delay,
+                           OnceClosure on_done,
+                           int num_posts_done = 0) {
+  const int kNumTimes = 128000;
+
   // Tasks should be running on a decent heart beat. Some platforms/bots however
-  // have a hard time posting+running *all* tasks before test timeout, allow
-  // those platforms to pass anyways so long as they kept a decent heartbeat up
-  // to that point.
+  // have a hard time posting+running *all* tasks before test timeout, add
+  // detailed logging for diagnosis where this flakes.
   const auto now = TimeTicks::Now();
-  EXPECT_LT(now - last_post_ticks, TestTimeouts::tiny_timeout());
-  if (posts_remaining == 0 ||
-      now - begin_ticks >= TestTimeouts::action_max_timeout()) {
-    if (posts_remaining > 0) {
-      LOG(ERROR) << "Slow test environment, bailing out early with "
-                 << posts_remaining << " tasks to go.";
-    }
+  const auto scheduling_delay = now - last_post_ticks;
+  if (scheduling_delay > slowest_delay)
+    slowest_delay = scheduling_delay;
+
+  if (num_posts_done == kNumTimes) {
+    std::move(on_done).Run();
+    return;
+  } else if (now - begin_ticks >= TestTimeouts::action_max_timeout()) {
+    ADD_FAILURE() << "Couldn't run all tasks."
+                  << "\nNumber of tasks remaining: "
+                  << kNumTimes - num_posts_done
+                  << "\nSlowest scheduling delay: " << slowest_delay
+                  << "\nAverage per task: "
+                  << (now - begin_ticks) / num_posts_done;
     std::move(on_done).Run();
     return;
   }
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, BindOnce(&PostNTasksThenQuit, begin_ticks, now,
-                          posts_remaining - 1, std::move(on_done)));
+  executor_task_runner->PostTask(
+      FROM_HERE,
+      BindOnce(&Post128KTasksThenQuit, Unretained(executor_task_runner),
+               begin_ticks, now, slowest_delay, std::move(on_done),
+               num_posts_done + 1));
 }
 
 #if defined(OS_WIN)
@@ -1278,19 +1290,20 @@
 // On Linux, the pipe buffer size is 64KiB by default. The bug caused one byte
 // accumulated in the pipe per two posts, so we should repeat 128K times to
 // reproduce the bug.
-//
-// Some platforms+bots are flakily unable to run that many tasks before test
-// timeout (see crbug.com/810077 and crbug.com/1188497). This test will bail
-// early and pass in those situations as long as it was able to keep a decent
-// heartbeat between tasks.
-TEST_P(SingleThreadTaskExecutorTypedTest, RecursivePostsDoNotFloodPipe) {
-  const int kNumTimes = 1 << 17;
+#if defined(OS_CHROMEOS)
+// TODO(crbug.com/1188497): This test is unreasonably slow on CrOS and flakily
+// times out (100x slower than other platforms which take < 1s to complete
+// it).
+#define MAYBE_RecursivePostsDoNotFloodPipe DISABLED_RecursivePostsDoNotFloodPipe
+#else
+#define MAYBE_RecursivePostsDoNotFloodPipe RecursivePostsDoNotFloodPipe
+#endif
+TEST_P(SingleThreadTaskExecutorTypedTest, MAYBE_RecursivePostsDoNotFloodPipe) {
   SingleThreadTaskExecutor executor(GetParam());
   const auto begin_ticks = TimeTicks::Now();
   RunLoop run_loop;
-  executor.task_runner()->PostTask(
-      FROM_HERE, BindOnce(&PostNTasksThenQuit, begin_ticks, begin_ticks,
-                          kNumTimes - 1, run_loop.QuitClosure()));
+  Post128KTasksThenQuit(executor.task_runner().get(), begin_ticks, begin_ticks,
+                        TimeDelta(), run_loop.QuitClosure());
   run_loop.Run();
 }
 
diff --git a/base/test/trace_event_analyzer.cc b/base/test/trace_event_analyzer.cc
index 66521c8..dae2070 100644
--- a/base/test/trace_event_analyzer.cc
+++ b/base/test/trace_event_analyzer.cc
@@ -149,7 +149,7 @@
         arg_numbers[it.key()] = it.value().GetDouble();
       }
       // Record all arguments as values.
-      arg_values[it.key()] = it.value().CreateDeepCopy();
+      arg_values[it.key()] = base::Value::ToUniquePtrValue(it.value().Clone());
     }
   }
 
@@ -184,7 +184,7 @@
                                std::unique_ptr<base::Value>* arg) const {
   const auto it = arg_values.find(name);
   if (it != arg_values.end()) {
-    *arg = it->second->CreateDeepCopy();
+    *arg = base::Value::ToUniquePtrValue(it->second->Clone());
     return true;
   }
   return false;
diff --git a/build/android/gyp/apkbuilder.py b/build/android/gyp/apkbuilder.py
index f1e6563..262cc834 100755
--- a/build/android/gyp/apkbuilder.py
+++ b/build/android/gyp/apkbuilder.py
@@ -350,6 +350,7 @@
   # Included via .build_config, so need to write it to depfile.
   depfile_deps.extend(x[0] for x in assets)
   depfile_deps.extend(x[0] for x in uncompressed_assets)
+  depfile_deps.append(options.resource_apk)
 
   # Bundle modules have a structure similar to APKs, except that resources
   # are compiled in protobuf format (instead of binary xml), and that some
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index 0600fdc..5ee57699 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -678,6 +678,10 @@
   return [c for c in configs if c['type'] == wanted_type]
 
 
+def DepPathsOfType(wanted_type, config_paths):
+  return [p for p in config_paths if GetDepConfig(p)['type'] == wanted_type]
+
+
 def GetAllDepsConfigsInOrder(deps_config_paths, filter_func=None):
   def GetDeps(path):
     config = GetDepConfig(path)
@@ -808,17 +812,22 @@
   return create_list(compressed), create_list(uncompressed), locale_paks
 
 
-def _ResolveGroups(configs):
+def _ResolveGroups(config_paths):
   """Returns a list of configs with all groups inlined."""
-  ret = list(configs)
+  ret = list(config_paths)
+  ret_set = set(config_paths)
   while True:
-    groups = DepsOfType('group', ret)
-    if not groups:
+    group_paths = DepPathsOfType('group', ret)
+    if not group_paths:
       return ret
-    for config in groups:
-      index = ret.index(config)
-      expanded_configs = [GetDepConfig(p) for p in config['deps_configs']]
-      ret[index:index + 1] = expanded_configs
+    for group_path in group_paths:
+      index = ret.index(group_path)
+      expanded_config_paths = []
+      for deps_config_path in GetDepConfig(group_path)['deps_configs']:
+        if not deps_config_path in ret_set:
+          expanded_config_paths.append(deps_config_path)
+      ret[index:index + 1] = expanded_config_paths
+      ret_set.update(expanded_config_paths)
 
 
 def _DepsFromPaths(dep_paths,
@@ -872,10 +881,11 @@
   about (i.e. we wish to prune all other branches that do not start from one of
   these).
   """
-  configs = [GetDepConfig(p) for p in dep_paths]
-  groups = DepsOfType('group', configs)
-  configs = _ResolveGroups(configs)
-  configs += groups
+  group_paths = DepPathsOfType('group', dep_paths)
+  config_paths = dep_paths
+  if group_paths:
+    config_paths = _ResolveGroups(dep_paths) + group_paths
+  configs = [GetDepConfig(p) for p in config_paths]
   if blocklist:
     configs = [c for c in configs if c['type'] not in blocklist]
   if allowlist:
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 307ecc1..edf5df7 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210505.0.1
+4.20210505.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 307ecc1..edf5df7 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210505.0.1
+4.20210505.2.1
diff --git a/cc/trees/layer_tree_host_unittest_checkerimaging.cc b/cc/trees/layer_tree_host_unittest_checkerimaging.cc
index 9801b17..6c47500 100644
--- a/cc/trees/layer_tree_host_unittest_checkerimaging.cc
+++ b/cc/trees/layer_tree_host_unittest_checkerimaging.cc
@@ -20,8 +20,9 @@
 
 class LayerTreeHostCheckerImagingTest : public LayerTreeTest {
  public:
-  LayerTreeHostCheckerImagingTest() : url_(GURL("https://example.com")),
-                                      ukm_source_id_(123) {}
+  LayerTreeHostCheckerImagingTest()
+      : url_(GURL("https://example.com")),
+        ukm_source_id_(ukm::AssignNewSourceId()) {}
 
   void BeginTest() override {
     layer_tree_host()->SetSourceURL(ukm_source_id_, url_);
@@ -36,8 +37,9 @@
     recorder->UpdateSourceURL(ukm_source_id_, url_);
 
     // Change the source to ensure any accumulated metrics are flushed.
-    impl->ukm_manager()->SetSourceId(200);
-    recorder->UpdateSourceURL(200, GURL("chrome://test2"));
+    ukm::SourceId newSourceId = ukm::AssignNewSourceId();
+    impl->ukm_manager()->SetSourceId(newSourceId);
+    recorder->UpdateSourceURL(newSourceId, GURL("chrome://test2"));
 
     const auto& entries = recorder->GetEntriesByName(kRenderingEvent);
     ASSERT_EQ(1u, entries.size());
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
index 91f6bf09..3ace23b 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantNavigationIntegrationTest.java
@@ -303,16 +303,4 @@
         waitUntilViewAssertionTrue(
                 withId(R.id.autofill_assistant), doesNotExist(), DEFAULT_MAX_TIME_TO_POLL);
     }
-
-    @Test
-    @MediumTest
-    public void navigateDuringOnboardingRemovesUI() throws Exception {
-        // Onboarding has not been accepted.
-        AutofillAssistantPreferencesUtil.setInitialPreferences(false);
-        startAutofillAssistantOnTab(TEST_PAGE_A);
-        waitUntilViewMatchesCondition(withId(R.id.button_init_ok), isCompletelyDisplayed());
-        mTestRule.loadUrl(getURL(TEST_PAGE_B));
-        waitUntilViewAssertionTrue(
-                withId(R.id.button_init_ok), doesNotExist(), DEFAULT_MAX_TIME_TO_POLL);
-    }
 }
diff --git a/chrome/android/features/cablev2_authenticator/BUILD.gn b/chrome/android/features/cablev2_authenticator/BUILD.gn
index b4e4ec7e..f3bb6ea 100644
--- a/chrome/android/features/cablev2_authenticator/BUILD.gn
+++ b/chrome/android/features/cablev2_authenticator/BUILD.gn
@@ -71,6 +71,8 @@
     "java/res/layout/cablev2_qr_dialog.xml",
     "java/res/layout/cablev2_qr_scan.xml",
     "java/res/layout/cablev2_serverlink.xml",
+    "java/res/layout-sw600dp/cablev2_serverlink.xml",
+    "java/res/layout-sw600dp/cablev2_error.xml",
     "java/res/layout/cablev2_usb_attached.xml",
     "java/res/values/attrs.xml",
     "java/res/values/styles.xml",
diff --git a/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_error.xml b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_error.xml
new file mode 100644
index 0000000..f242e2b
--- /dev/null
+++ b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_error.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
+    a:layout_width="match_parent"
+    a:layout_height="match_parent"
+    a:gravity="center"
+    a:orientation="vertical">
+
+  <LinearLayout
+      a:layout_width="match_parent"
+      a:layout_weight="2"
+      a:layout_height="0dp"
+      a:layout_marginTop="70dp"
+      a:orientation="vertical"
+      a:gravity="center">
+
+    <TextView
+        a:id="@+id/error_title"
+        a:textSize="36sp"
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="104dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_error_title"/>
+
+    <!-- This is a semantically-meaningless picture of a sad tab that
+         screen readers can ignore. Thus contentDescription is null. -->
+    <ImageView
+        a:layout_width="210dp"
+        a:layout_height="128dp"
+        a:layout_marginTop="52dp"
+        a:layout_marginBottom="35dp"
+        a:contentDescription="@null"
+        a:gravity="center_horizontal"
+        a:src="@drawable/error_icon"/>
+
+    <TextView
+        a:id="@+id/error_description"
+        style="@style/TextAppearance.TextMedium.Secondary"
+        a:layout_marginTop="4dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginRight="40dp"
+        a:layout_width="wrap_content"
+        a:layout_height="wrap_content"
+        a:gravity="center_horizontal"/>
+
+    <TextView
+        a:id="@+id/error_code"
+        style="@style/TextAppearance.TextMedium.Disabled"
+        a:layout_marginTop="4dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginRight="40dp"
+        a:layout_width="wrap_content"
+        a:layout_height="wrap_content"
+        a:gravity="center_horizontal"/>
+
+  </LinearLayout>
+
+  <LinearLayout
+      a:layout_width="match_parent"
+      a:layout_height="0dp"
+      a:layout_weight="1"
+      a:gravity="bottom|center">
+
+    <org.chromium.ui.widget.ButtonCompat
+        a:id="@+id/error_close"
+        style="@style/TextButton"
+        a:layout_width="wrap_content"
+        a:layout_height="wrap_content"
+        a:gravity="bottom|center"
+        a:layout_marginBottom="35dp"
+        a:text="@string/cablev2_error_close"/>
+
+  </LinearLayout>
+</LinearLayout>
diff --git a/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_serverlink.xml b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_serverlink.xml
new file mode 100644
index 0000000..f7703322
--- /dev/null
+++ b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_serverlink.xml
@@ -0,0 +1,58 @@
+<?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. -->
+
+<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    a:layout_width="match_parent"
+    a:layout_height="match_parent"
+    a:orientation="vertical"
+    a:gravity="center"
+    xmlns:tools="http://schemas.android.com/tools">
+
+  <TextView
+      a:layout_width="match_parent"
+      a:layout_height="wrap_content"
+      a:layout_marginTop="-104dp"
+      a:layout_marginLeft="24dp"
+      a:layout_marginRight="24dp"
+      a:gravity="center_horizontal"
+      a:text="@string/cablev2_serverlink_connecting_to_your_device"
+      a:textSize="36sp" />
+
+  <RelativeLayout
+      a:layout_width="wrap_content"
+      a:layout_height="wrap_content"
+      a:layoutDirection="ltr"
+      a:layout_marginTop="52dp"
+      a:layout_marginLeft="24dp"
+      a:layout_marginRight="24dp"
+      a:layout_gravity="center_horizontal">
+    <ImageView
+        a:id="@+id/spinner"
+        a:contentDescription="@null"
+        a:layout_width="wrap_content"
+        a:layout_height="wrap_content"
+        a:layout_gravity="center"
+        a:layout_centerInParent="true"
+        a:layout_centerVertical="true" />
+    <ImageView
+        a:contentDescription="@null"
+        a:layout_height="wrap_content"
+        a:layout_width="wrap_content"
+        a:src="@drawable/ic_lock_googblue_48dp"
+        a:layout_centerInParent="true"
+        a:layout_centerVertical="true" />
+  </RelativeLayout>
+
+  <TextView
+      a:id="@+id/status_text"
+      a:layout_width="wrap_content"
+      a:layout_height="wrap_content"
+      a:layout_marginTop="4dp"
+      a:layout_gravity="center_horizontal"
+      a:padding="0px"
+      a:textSize="24sp" />
+
+</LinearLayout>
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index eabd15bf..664e199c 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -82,6 +82,7 @@
         implements NewTabPageLayout.ScrollDelegate, ContextMenuManager.TouchEnabledDelegate,
                    TemplateUrlServiceObserver, ListMenu.Delegate,
                    EnhancedProtectionPromoStateListener, IdentityManager.Observer {
+    private static final String TAG = "FeedSurfaceMediator";
     @VisibleForTesting
     public static final String FEED_CONTENT_FIRST_LOADED_TIME_MS_UMA = "FeedContentFirstLoadedTime";
     private static final int INTEREST_FEED_HEADER_POSITION = 0;
@@ -223,6 +224,7 @@
     private int mThumbnailWidth;
     private int mThumbnailHeight;
     private int mThumbnailScrollY;
+    private int mRestoreTabId;
 
     /** The model representing feed-related cog menu items. */
     private ModelList mFeedMenuModel;
@@ -361,6 +363,7 @@
     void restoreSavedInstanceState(String json) {
         ScrollState state = ScrollState.fromJson(json);
         if (state == null) return;
+        mRestoreTabId = state.tabId;
         if (mSectionHeaderModel != null) {
             mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, state.tabId);
         }
@@ -397,13 +400,17 @@
             mCoordinator.initializeIph();
             mSigninManager.getIdentityManager().addObserver(this);
 
-            mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, 0);
             mSectionHeaderModel.set(
                     SectionHeaderListProperties.MENU_MODEL_LIST_KEY, mFeedMenuModel);
             mSectionHeaderModel.set(
                     SectionHeaderListProperties.MENU_DELEGATE_KEY, this::onItemSelected);
 
             setUpWebFeedTab();
+
+            // Set the current tab index to what restoreSavedInstanceState had.
+            if (mTabToStreamMap.size() <= mRestoreTabId) mRestoreTabId = 0;
+            mSectionHeaderModel.set(
+                    SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, mRestoreTabId);
         } else {
             // Show feed if there is no header that would allow user to hide feed.
             // This is currently only relevant for the two panes start surface.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index f5b7066..7f109f8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -45,6 +45,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.annotations.UsedByReflection;
+import org.chromium.base.jank_tracker.JankTracker;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
@@ -299,6 +300,11 @@
     // This is the cached value of mIntentHandler#shouldIgnoreIntent and shouldn't be read directly.
     // Use #shouldIgnoreIntent instead.
     private Boolean mShouldIgnoreIntent;
+    /*
+     *  Listens to FrameMetrics and records jank metrics.
+     */
+    private JankTracker mJankTracker;
+
     // Supplier for a dependency to inform about the type of intent used to launch Chrome.
     private OneshotSupplierImpl<ToolbarIntentMetadata> mIntentMetadataOneshotSupplier =
             new OneshotSupplierImpl<>();
@@ -1556,6 +1562,10 @@
             setTrackColdStartupMetrics(true);
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            mJankTracker = new JankTracker(this);
+        }
+
         supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
 
         IncognitoTabHostRegistry.getInstance().register(mIncognitoTabHost);
@@ -2344,6 +2354,11 @@
             mStartupPaintPreviewHelperSupplier.destroy();
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mJankTracker != null) {
+            mJankTracker.destroy();
+            mJankTracker = null;
+        }
+
         IncognitoTabHostRegistry.getInstance().unregister(mIncognitoTabHost);
 
         TabObscuringHandler tabObscuringHandler = getTabObscuringHandler();
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 7fd525f..eec795b 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -321,8 +321,8 @@
   <message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE" desc="Label for informing the user on ways to retrieve an eSim profile">
     Scan QR code using device camera or enter activation code provided by your carrier.
   </message>
-  <message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES" desc="Label for informing the user on ways to retrieve an eSim profile after no eSIM profiles have been found.">
-    No profiles were discovered. To setup a new network, scan QR code using device camera or enter activation code provided by your carrier.
+  <message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES" desc="Label for informing the user on ways to retrieve an eSIM profile after no eSIM profiles have been found.">
+    No profiles were discovered. To set up a new network, scan QR code using device camera or enter activation code provided by your carrier.
   </message>
   <message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_ENTER_ACTIVATION_CODE" desc="Label for informing the user to enter activation code when no media devices have been found">
     Please enter your activation code
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES.png.sha1
index 3211d0e..9877dfe 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_NO_PROFILES.png.sha1
@@ -1 +1 @@
-1d5d372f5f8b4c6b4204871a459416e782363e09
\ No newline at end of file
+9f75335a38f18676df64aa1360878f6d8f827a8b
\ No newline at end of file
diff --git a/chrome/app/media_router_strings.grdp b/chrome/app/media_router_strings.grdp
index facd09d..a77968c 100644
--- a/chrome/app/media_router_strings.grdp
+++ b/chrome/app/media_router_strings.grdp
@@ -110,6 +110,15 @@
   <message name="IDS_MEDIA_ROUTER_AVAILABLE_SPECIFIC_SITES" desc="Text shown as the status of a Cast device that is not compatible with the selected website but is compatible with other video websites.">
     Available for specific video sites
   </message>
+  <message name="IDS_MEDIA_ROUTER_CASTING_DESKTOP" desc="The status text of a Cast device to which we are mirroring the contents of the user's entire screen.">
+    Casting desktop
+  </message>
+  <message name="IDS_MEDIA_ROUTER_CASTING_TAB" desc="The status text of a Cast device to which we are mirroring the contents of a browser tab.">
+    Casting tab
+  </message>
+  <message name="IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION" desc="Description shown to indicate that a website is being presented to another display.">
+    Presenting (<ph name="PAGE_ORIGIN">$1<ex>example.com</ex></ph>)
+  </message>
 
   <!-- File Dialog -->
   <message name="IDS_MEDIA_ROUTER_FILE_DIALOG_AUDIO_VIDEO_FILTER" desc="Label for the drop down menu in the file selection dialog to show only audio and
@@ -146,9 +155,6 @@
   </message>
 
   <!-- Wired Display Media Route Provider -->
-  <message name="IDS_MEDIA_ROUTER_WIRED_DISPLAY_ROUTE_DESCRIPTION" desc="Description shown to indicate that a website is being presented to another display.">
-    Presenting (<ph name="PAGE_ORIGIN">$1<ex>example.com</ex></ph>)
-  </message>
   <message name="IDS_MEDIA_ROUTER_WIRED_DISPLAY_SINK_NAME" desc="Name shown for a wired display that can be used as a Cast destination.">
     Display <ph name="DISPLAY_ID">$1<ex>1</ex></ph>
   </message>
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_DESKTOP.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_DESKTOP.png.sha1
new file mode 100644
index 0000000..36fc67d
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_DESKTOP.png.sha1
@@ -0,0 +1 @@
+772ab8f7be2ecba3ef72d76d82204b8983c0d658
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_TAB.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_TAB.png.sha1
new file mode 100644
index 0000000..e108f1f
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_CASTING_TAB.png.sha1
@@ -0,0 +1 @@
+657ad52d57792aa97543cb36e607b14ed2589923
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..fbf2eae42
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+59a09584f8cd0dd996c1b3b166413b5e00ebee97
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 7aada7d..89d08e3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4311,6 +4311,10 @@
       "apps/app_service/publishers/web_apps_crosapi_factory.h",
       "apps/app_service/uninstall_dialog.cc",
       "apps/app_service/uninstall_dialog.h",
+      "apps/app_service/webapk/webapk_install_queue.cc",
+      "apps/app_service/webapk/webapk_install_queue.h",
+      "apps/app_service/webapk/webapk_install_task.cc",
+      "apps/app_service/webapk/webapk_install_task.h",
       "apps/app_service/webapk/webapk_manager.cc",
       "apps/app_service/webapk/webapk_manager.h",
       "apps/icon_standardizer.cc",
@@ -4621,6 +4625,8 @@
       "//components/services/font:lib",
       "//components/services/font/public/mojom",
       "//components/user_manager",
+      "//components/webapk:proto",
+      "//third_party/smhasher:murmurhash2",
       "//ui/chromeos",
       "//ui/events/ozone",
       "//ui/ozone",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 283aafb9..2b547d5f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5713,6 +5713,12 @@
      kOsDesktop | kOsAndroid,
      FEATURE_VALUE_TYPE(features::kPrivacySandboxSettings)},
 
+    {"privacy-sandbox-settings-2",
+     flag_descriptions::kPrivacySandboxSettings2Name,
+     flag_descriptions::kPrivacySandboxSettings2Description,
+     kOsDesktop | kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kPrivacySandboxSettings2)},
+
 #if defined(OS_ANDROID)
     {"metrics-settings-android", flag_descriptions::kMetricsSettingsAndroidName,
      flag_descriptions::kMetricsSettingsAndroidDescription, kOsAndroid,
diff --git a/chrome/browser/accessibility/caption_controller.cc b/chrome/browser/accessibility/caption_controller.cc
index 7a00397..6843832 100644
--- a/chrome/browser/accessibility/caption_controller.cc
+++ b/chrome/browser/accessibility/caption_controller.cc
@@ -96,10 +96,12 @@
     StopLiveCaption();
   }
 
+  // BrowserAccessibilityState can outlive |this|, use WeakPtr to ensure that
+  // callback is not called on destroyed object.
   content::BrowserAccessibilityState::GetInstance()
       ->AddUIThreadHistogramCallback(base::BindOnce(
           &CaptionController::UpdateAccessibilityCaptionHistograms,
-          base::Unretained(this)));
+          weak_ptr_factory_.GetWeakPtr()));
 }
 
 void CaptionController::OnLiveCaptionEnabledChanged() {
diff --git a/chrome/browser/accessibility/caption_controller.h b/chrome/browser/accessibility/caption_controller.h
index 850fed1..cbf2bdc6 100644
--- a/chrome/browser/accessibility/caption_controller.h
+++ b/chrome/browser/accessibility/caption_controller.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "chrome/common/caption.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/soda/constants.h"
@@ -113,6 +114,8 @@
   // feature being enabled--we wait for SODA to download first. This flag
   // ensures that the UI is not constructed or deconstructed twice.
   bool is_ui_constructed_ = false;
+
+  base::WeakPtrFactory<CaptionController> weak_ptr_factory_{this};
 };
 
 }  // namespace captions
diff --git a/chrome/browser/android/autofill_assistant/client_android.cc b/chrome/browser/android/autofill_assistant/client_android.cc
index 38f5dcd..dd40e669 100644
--- a/chrome/browser/android/autofill_assistant/client_android.cc
+++ b/chrome/browser/android/autofill_assistant/client_android.cc
@@ -99,6 +99,15 @@
   return base::android::ScopedJavaLocalRef<jobject>(java_object_);
 }
 
+bool ClientAndroid::IsRunning() const {
+  return controller_ != nullptr;
+}
+
+bool ClientAndroid::IsVisible() const {
+  return ui_controller_android_ != nullptr &&
+         ui_controller_android_->IsAttached();
+}
+
 bool ClientAndroid::Start(
     const GURL& url,
     std::unique_ptr<TriggerContext> trigger_context,
diff --git a/chrome/browser/android/autofill_assistant/client_android.h b/chrome/browser/android/autofill_assistant/client_android.h
index bb80068c..3574757 100644
--- a/chrome/browser/android/autofill_assistant/client_android.h
+++ b/chrome/browser/android/autofill_assistant/client_android.h
@@ -47,6 +47,12 @@
   // Returns the corresponding Java AutofillAssistantClient.
   base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
 
+  // Returns whether a flow is currently running.
+  bool IsRunning() const;
+
+  // Returns whether UI is currently being displayed to the user.
+  bool IsVisible() const;
+
   bool Start(const GURL& url,
              std::unique_ptr<TriggerContext> trigger_context,
              std::unique_ptr<Service> test_service_to_inject,
diff --git a/chrome/browser/android/autofill_assistant/starter_android.cc b/chrome/browser/android/autofill_assistant/starter_android.cc
index 2803029..eb782c5 100644
--- a/chrome/browser/android/autofill_assistant/starter_android.cc
+++ b/chrome/browser/android/autofill_assistant/starter_android.cc
@@ -275,6 +275,22 @@
       trigger_script);
 }
 
+bool StarterAndroid::IsRegularScriptRunning() const {
+  auto* client_android = ClientAndroid::FromWebContents(web_contents_);
+  if (!client_android) {
+    return false;
+  }
+  return client_android->IsRunning();
+}
+
+bool StarterAndroid::IsRegularScriptVisible() const {
+  auto* client_android = ClientAndroid::FromWebContents(web_contents_);
+  if (!client_android) {
+    return false;
+  }
+  return client_android->IsVisible();
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(StarterAndroid)
 
 }  // namespace autofill_assistant
diff --git a/chrome/browser/android/autofill_assistant/starter_android.h b/chrome/browser/android/autofill_assistant/starter_android.h
index 47f3c6a..d3f2d90 100644
--- a/chrome/browser/android/autofill_assistant/starter_android.h
+++ b/chrome/browser/android/autofill_assistant/starter_android.h
@@ -52,6 +52,8 @@
       GURL url,
       std::unique_ptr<TriggerContext> trigger_context,
       const base::Optional<TriggerScriptProto>& trigger_script) override;
+  bool IsRegularScriptRunning() const override;
+  bool IsRegularScriptVisible() const override;
   WebsiteLoginManager* GetWebsiteLoginManager() const override;
   version_info::Channel GetChannel() const override;
   bool GetFeatureModuleInstalled() const override;
diff --git a/chrome/browser/apps/app_service/DEPS b/chrome/browser/apps/app_service/DEPS
index 70d27484..eb84316 100644
--- a/chrome/browser/apps/app_service/DEPS
+++ b/chrome/browser/apps/app_service/DEPS
@@ -2,6 +2,7 @@
   "+components/services/app_service/app_service_impl.h",
   "+components/services/app_service/public",
   "+components/full_restore",
+  "+components/webapk",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc
index d846f0d..02090efa 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -41,6 +41,8 @@
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
 #include "components/arc/intent_helper/intent_constants.h"
+#include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/app_permissions.mojom.h"
 #include "components/arc/mojom/compatibility_mode.mojom.h"
 #include "components/arc/mojom/file_system.mojom.h"
@@ -670,8 +672,8 @@
     return;
   }
 
-  UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
-                            user_interaction_type.value());
+  arc::ArcMetricsService::RecordArcUserInteraction(
+      profile_, user_interaction_type.value());
 
   ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
   if (!prefs) {
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_queue.cc b/chrome/browser/apps/app_service/webapk/webapk_install_queue.cc
new file mode 100644
index 0000000..897c7dd
--- /dev/null
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_queue.cc
@@ -0,0 +1,76 @@
+// Copyright 2021 The Chromium 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/apps/app_service/webapk/webapk_install_queue.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/apps/app_service/webapk/webapk_install_task.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/arc/arc_service_manager.h"
+#include "components/arc/mojom/webapk.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+
+namespace apps {
+
+WebApkInstallQueue::WebApkInstallQueue(Profile* profile)
+    : profile_(profile), connection_ready_(false) {
+  arc::ArcServiceManager* arc_service_manager = arc::ArcServiceManager::Get();
+  DCHECK(arc_service_manager);
+  arc_service_manager->arc_bridge_service()->webapk()->AddObserver(this);
+}
+
+WebApkInstallQueue::~WebApkInstallQueue() {
+  arc::ArcServiceManager* arc_service_manager = arc::ArcServiceManager::Get();
+  if (arc_service_manager) {
+    arc_service_manager->arc_bridge_service()->webapk()->RemoveObserver(this);
+  }
+}
+
+void WebApkInstallQueue::Install(const std::string& app_id) {
+  pending_installs_.push_back(
+      std::make_unique<WebApkInstallTask>(profile_, app_id));
+  PostMaybeStartNext();
+}
+
+void WebApkInstallQueue::PostMaybeStartNext() {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&WebApkInstallQueue::MaybeStartNext,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+void WebApkInstallQueue::MaybeStartNext() {
+  if (pending_installs_.empty() || current_install_ || !connection_ready_) {
+    return;
+  }
+
+  current_install_ = std::move(pending_installs_.front());
+  pending_installs_.pop_front();
+
+  current_install_->Start(base::BindOnce(
+      &WebApkInstallQueue::OnInstallCompleted, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void WebApkInstallQueue::OnInstallCompleted(bool success) {
+  current_install_.reset();
+  PostMaybeStartNext();
+}
+
+void WebApkInstallQueue::OnConnectionReady() {
+  // Only start installing when WebApkInstance is ready, since installs cannot
+  // complete without it.
+  connection_ready_ = true;
+  PostMaybeStartNext();
+}
+
+void WebApkInstallQueue::OnConnectionClosed() {
+  connection_ready_ = false;
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_queue.h b/chrome/browser/apps/app_service/webapk/webapk_install_queue.h
new file mode 100644
index 0000000..c2057294
--- /dev/null
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_queue.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The Chromium 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_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_QUEUE_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_QUEUE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/circular_deque.h"
+#include "base/memory/weak_ptr.h"
+#include "components/arc/mojom/webapk.mojom-forward.h"
+#include "components/arc/session/connection_observer.h"
+
+class Profile;
+
+namespace apps {
+
+class WebApkInstallTask;
+
+class WebApkInstallQueue
+    : public arc::ConnectionObserver<arc::mojom::WebApkInstance> {
+ public:
+  explicit WebApkInstallQueue(Profile* profile);
+  WebApkInstallQueue(const WebApkInstallQueue&) = delete;
+  WebApkInstallQueue& operator=(const WebApkInstallQueue&) = delete;
+
+  ~WebApkInstallQueue() override;
+
+  void Install(const std::string& app_id);
+
+  // arc::ConnectionObserver<arc::mojom::WebApkInstance> overrides:
+  void OnConnectionReady() override;
+  void OnConnectionClosed() override;
+
+ private:
+  void PostMaybeStartNext();
+  void MaybeStartNext();
+  void OnInstallCompleted(bool success);
+
+  Profile* profile_;
+  base::circular_deque<std::unique_ptr<WebApkInstallTask>> pending_installs_;
+  std::unique_ptr<WebApkInstallTask> current_install_;
+  bool connection_ready_;
+
+  base::WeakPtrFactory<WebApkInstallQueue> weak_ptr_factory_{this};
+};
+
+}  // namespace apps
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_QUEUE_H_
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task.cc b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
new file mode 100644
index 0000000..75e90b8b
--- /dev/null
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
@@ -0,0 +1,279 @@
+// Copyright 2021 The Chromium 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/apps/app_service/webapk/webapk_install_task.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/components/app_icon_manager.h"
+#include "chrome/browser/web_applications/components/app_registrar.h"
+#include "chrome/browser/web_applications/components/web_app_provider_base.h"
+#include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/arc/arc_service_manager.h"
+#include "components/arc/mojom/webapk.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "components/version_info/version_info.h"
+#include "components/webapk/webapk.pb.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/fetch_api.mojom-shared.h"
+#include "third_party/smhasher/src/MurmurHash2.h"
+#include "url/gurl.h"
+
+namespace {
+
+// The MIME type of the POST data sent to the server.
+constexpr char kProtoMimeType[] = "application/x-protobuf";
+
+constexpr char kRequesterPackageName[] = "org.chromium.arc.webapk";
+
+const char kMinimumIconSize = 64;
+
+// The seed to use when taking the murmur2 hash of the icon.
+const uint64_t kMurmur2HashSeed = 0;
+
+constexpr net::NetworkTrafficAnnotationTag kWebApksTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("webapk_minter_install_request",
+                                        R"(
+        semantics {
+          sender: "WebAPKs"
+          description:
+            "Chrome OS generates small Android apps called 'WebAPKs' which "
+            "represent the Progressive Web Apps installed in Chrome OS. These "
+            "apps are installed in the ARC Android environment and used to "
+            "improve integration between ARC and Chrome OS. This network "
+            "request sends the details for a single web app to the WebAPK "
+            "service, which returns details about the WebAPK to install."
+          trigger: "Installing or updating a progressive web app."
+          data:
+            "The contents of the web app manifest for the web app, plus system "
+            "information needed to generate the app."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          cookies_store: "N/A"
+          setting: "No setting apart from disabling ARC"
+          policy_exception_justification = "Not implemented"
+        }
+      )");
+
+GURL GetServerUrl() {
+  // While the code is pre-Canary quality, a custom server URL must be supplied.
+  std::string server_url =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kWebApkServerUrl);
+  CHECK(!server_url.empty())
+      << "A WebAPK server URL must be provided with --webapk-server-url";
+  return GURL(server_url);
+}
+
+// Attaches icon PNG data and hash to an existing icon entry, and then
+// serializes and returns the entire proto. Should be called on a worker thread.
+std::string AddIconDataAndSerializeProto(std::unique_ptr<webapk::WebApk> webapk,
+                                         std::vector<uint8_t> icon_data) {
+  base::AssertLongCPUWorkAllowed();
+  DCHECK_EQ(webapk->manifest().icons_size(), 1);
+
+  webapk::Image* icon = webapk->mutable_manifest()->mutable_icons(0);
+  icon->set_image_data(icon_data.data(), icon_data.size());
+
+  uint64_t icon_hash =
+      MurmurHash64A(icon_data.data(), icon_data.size(), kMurmur2HashSeed);
+  icon->set_hash(base::NumberToString(icon_hash));
+
+  std::string serialized_proto;
+  webapk->SerializeToString(&serialized_proto);
+
+  return serialized_proto;
+}
+
+}  // namespace
+
+namespace apps {
+
+WebApkInstallTask::WebApkInstallTask(Profile* profile,
+                                     const std::string& app_id)
+    : profile_(profile),
+      web_app_provider_(web_app::WebAppProviderBase::GetProviderBase(profile_)),
+      app_id_(app_id) {
+  DCHECK(web_app_provider_);
+}
+
+WebApkInstallTask::~WebApkInstallTask() = default;
+
+void WebApkInstallTask::Start(ResultCallback callback) {
+  VLOG(1) << "Generating WebAPK for app: " << app_id_;
+
+  auto& registrar = web_app_provider_->registrar();
+
+  if (!registrar.IsInstalled(app_id_)) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  auto& icon_manager = web_app_provider_->icon_manager();
+  base::Optional<web_app::AppIconManager::IconSizeAndPurpose>
+      icon_size_and_purpose = icon_manager.FindIconMatchBigger(
+          app_id_, {IconPurpose::MASKABLE, IconPurpose::ANY}, kMinimumIconSize);
+
+  if (!icon_size_and_purpose) {
+    LOG(ERROR) << "Could not find suitable icon";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  // We need to send a URL for the icon, but it's possible the local image we're
+  // sending has been resized and so doesn't exactly match any of the images in
+  // the manifest. Since we can't be perfect, it's okay to be roughly correct
+  // and just send any URL of the correct purpose.
+  const auto& icon_infos = registrar.GetAppIconInfos(app_id_);
+  auto it = std::find_if(
+      icon_infos.begin(), icon_infos.end(),
+      [&icon_size_and_purpose](const WebApplicationIconInfo& info) {
+        return info.purpose == icon_size_and_purpose->purpose;
+      });
+
+  if (it == icon_infos.end()) {
+    LOG(ERROR) << "Could not find URL for icon";
+    std::move(callback).Run(false);
+    return;
+  }
+  std::string icon_url = it->url.spec();
+
+  std::unique_ptr<webapk::WebApk> webapk = std::make_unique<webapk::WebApk>();
+  webapk->set_manifest_url(registrar.GetAppManifestUrl(app_id_).spec());
+  webapk->set_requester_application_package(kRequesterPackageName);
+  webapk->set_requester_application_version(version_info::GetVersionNumber());
+  // TODO(crbug.com/1198433): Fetch real Android ABI.
+  webapk->set_android_abi("x86_64");
+  webapk->add_update_reasons(webapk::WebApk::NONE);
+
+  webapk::WebAppManifest* web_app_manifest = webapk->mutable_manifest();
+  web_app_manifest->set_short_name(registrar.GetAppShortName(app_id_));
+  web_app_manifest->set_start_url(registrar.GetAppStartUrl(app_id_).spec());
+  web_app_manifest->add_scopes(registrar.GetAppScope(app_id_).spec());
+
+  // TODO(crbug.com/1198433): Fill in Share Target.
+
+  webapk::Image* image = web_app_manifest->add_icons();
+  image->set_src(std::move(icon_url));
+  image->add_purposes(icon_size_and_purpose->purpose == IconPurpose::MASKABLE
+                          ? webapk::Image::MASKABLE
+                          : webapk::Image::ANY);
+  image->add_usages(webapk::Image::PRIMARY_ICON);
+
+  icon_manager.ReadSmallestCompressedIcon(
+      app_id_, {icon_size_and_purpose->purpose}, icon_size_and_purpose->size_px,
+      base::BindOnce(&WebApkInstallTask::OnLoadedIcon,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(webapk),
+                     std::move(callback)));
+}
+
+void WebApkInstallTask::OnLoadedIcon(std::unique_ptr<webapk::WebApk> webapk,
+                                     ResultCallback callback,
+                                     IconPurpose purpose,
+                                     std::vector<uint8_t> data) {
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+      base::BindOnce(AddIconDataAndSerializeProto, std::move(webapk),
+                     std::move(data)),
+      base::BindOnce(&WebApkInstallTask::OnProtoSerialized,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void WebApkInstallTask::OnProtoSerialized(ResultCallback callback,
+                                          std::string serialized_proto) {
+  GURL server_url = GetServerUrl();
+
+  // TODO(crbug.com/1198433): Add timeout.
+  auto request = std::make_unique<network::ResourceRequest>();
+  request->url = server_url;
+  request->method = "POST";
+  request->load_flags = net::LOAD_DISABLE_CACHE;
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  auto* url_loader_factory = profile_->GetDefaultStoragePartition()
+                                 ->GetURLLoaderFactoryForBrowserProcess()
+                                 .get();
+
+  url_loader_ = network::SimpleURLLoader::Create(std::move(request),
+                                                 kWebApksTrafficAnnotation);
+  url_loader_->AttachStringForUpload(std::move(serialized_proto),
+                                     kProtoMimeType);
+  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory,
+      base::BindOnce(&WebApkInstallTask::OnUrlLoaderComplete,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void WebApkInstallTask::OnUrlLoaderComplete(
+    ResultCallback callback,
+    std::unique_ptr<std::string> response_body) {
+  int response_code = -1;
+  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
+    response_code = url_loader_->ResponseInfo()->headers->response_code();
+
+  if (!response_body || response_code != net::HTTP_OK) {
+    LOG(WARNING) << "WebAPK server returned response code " << response_code;
+    std::move(callback).Run(false);
+    return;
+  }
+
+  auto response = std::make_unique<webapk::WebApkResponse>();
+  if (!response->ParseFromString(*response_body)) {
+    LOG(WARNING) << "Failed to parse WebApkResponse proto";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  VLOG(1) << "Installing WebAPK: " << response->package_name();
+
+  auto* arc_service_manager = arc::ArcServiceManager::Get();
+  DCHECK(arc_service_manager);
+  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
+      arc_service_manager->arc_bridge_service()->webapk(), InstallWebApk);
+
+  if (!instance) {
+    LOG(ERROR) << "WebApkInstance is not ready";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  auto& registrar = web_app_provider_->registrar();
+
+  int webapk_version;
+  base::StringToInt(response->version(), &webapk_version);
+  instance->InstallWebApk(
+      response->package_name(), webapk_version,
+      registrar.GetAppShortName(app_id_), response->token(),
+      base::BindOnce(&WebApkInstallTask::OnInstallComplete,
+                     weak_ptr_factory_.GetWeakPtr(), response->package_name(),
+                     std::move(callback)));
+}
+
+void WebApkInstallTask::OnInstallComplete(
+    const std::string& package_name,
+    ResultCallback callback,
+    arc::mojom::WebApkInstallResult result) {
+  VLOG(1) << "WebAPK installation finished with result " << result;
+  std::move(callback).Run(result == arc::mojom::WebApkInstallResult::kSuccess);
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task.h b/chrome/browser/apps/app_service/webapk/webapk_install_task.h
new file mode 100644
index 0000000..d822283
--- /dev/null
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium 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_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_TASK_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_TASK_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/web_applications/components/web_application_info.h"
+#include "components/arc/mojom/webapk.mojom-forward.h"
+
+class Profile;
+
+namespace network {
+class SimpleURLLoader;
+}
+
+namespace webapk {
+class WebApk;
+}
+
+namespace web_app {
+class WebAppProviderBase;
+}
+
+namespace apps {
+
+class WebApkInstallTask {
+  using ResultCallback = base::OnceCallback<void(bool success)>;
+
+ public:
+  WebApkInstallTask(Profile* profile, const std::string& app_id);
+  WebApkInstallTask(const WebApkInstallTask&) = delete;
+  WebApkInstallTask& operator=(const WebApkInstallTask&) = delete;
+
+  ~WebApkInstallTask();
+
+  void Start(ResultCallback callback);
+
+ private:
+  void OnLoadedIcon(std::unique_ptr<webapk::WebApk> webapk,
+                    ResultCallback callback,
+                    IconPurpose purpose,
+                    std::vector<uint8_t> data);
+  void OnProtoSerialized(ResultCallback callback, std::string serialized_proto);
+  void OnUrlLoaderComplete(ResultCallback callback,
+                           std::unique_ptr<std::string> response_body);
+  void OnInstallComplete(const std::string& package_name,
+                         ResultCallback callback,
+                         arc::mojom::WebApkInstallResult result);
+
+  Profile* const profile_;
+  web_app::WebAppProviderBase* web_app_provider_;
+
+  const std::string app_id_;
+  std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+  base::WeakPtrFactory<WebApkInstallTask> weak_ptr_factory_{this};
+};
+
+}  // namespace apps
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_WEBAPK_WEBAPK_INSTALL_TASK_H_
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc b/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc
new file mode 100644
index 0000000..8187705
--- /dev/null
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc
@@ -0,0 +1,224 @@
+// Copyright 2021 The Chromium 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/apps/app_service/webapk/webapk_install_task.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/test/bind.h"
+#include "chrome/browser/apps/app_service/app_service_test.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
+#include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/test/test_web_app_provider.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/arc/arc_service_manager.h"
+#include "components/arc/mojom/webapk.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "components/arc/test/fake_webapk_instance.h"
+#include "components/webapk/webapk.pb.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kTestAppUrl[] = "https://www.example.com/";
+constexpr char kTestAppIcon[] = "https://www.example.com/icon.png";
+constexpr char kTestManifestUrl[] = "https://www.example.com/manifest.json";
+const std::u16string kTestAppTitle = u"Test App";
+
+constexpr char kServerPath[] = "/webapk";
+
+constexpr char kToken[] = "opaque token";
+
+std::unique_ptr<net::test_server::HttpResponse> BuildValidWebApkResponse(
+    std::string package_name) {
+  auto webapk_response = std::make_unique<webapk::WebApkResponse>();
+  webapk_response->set_package_name(std::move(package_name));
+  webapk_response->set_version("1");
+  webapk_response->set_token(kToken);
+
+  std::string response_content;
+  webapk_response->SerializeToString(&response_content);
+
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_OK);
+  response->set_content(response_content);
+
+  return response;
+}
+
+std::unique_ptr<net::test_server::HttpResponse> BuildFailedResponse() {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_BAD_REQUEST);
+  return response;
+}
+
+std::unique_ptr<WebApplicationInfo> BuildWebAppInfo() {
+  auto app_info = std::make_unique<WebApplicationInfo>();
+  app_info->start_url = GURL(kTestAppUrl);
+  app_info->scope = GURL(kTestAppUrl);
+  app_info->title = kTestAppTitle;
+  app_info->manifest_url = GURL(kTestManifestUrl);
+  WebApplicationIconInfo icon;
+  icon.square_size_px = 64;
+  icon.purpose = IconPurpose::ANY;
+  icon.url = GURL(kTestAppIcon);
+  app_info->icon_infos.push_back(icon);
+
+  return app_info;
+}
+}  // namespace
+
+class WebApkInstallTaskTest : public testing::Test {
+  using WebApkResponseBuilder =
+      base::RepeatingCallback<std::unique_ptr<net::test_server::HttpResponse>(
+          void)>;
+
+ public:
+  WebApkInstallTaskTest()
+      : task_environment_(content::BrowserTaskEnvironment::MainThreadType::IO) {
+  }
+  WebApkInstallTaskTest(const WebApkInstallTaskTest&) = delete;
+  WebApkInstallTaskTest& operator=(const WebApkInstallTaskTest&) = delete;
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    extensions::TestExtensionSystem* extension_system(
+        static_cast<extensions::TestExtensionSystem*>(
+            extensions::ExtensionSystem::Get(&profile_)));
+    extension_service_ = extension_system->CreateExtensionService(
+        base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
+    extension_service_->Init();
+
+    app_service_test_.SetUp(&profile_);
+
+    auto* const provider = web_app::TestWebAppProvider::Get(&profile_);
+    provider->SetRunSubsystemStartupTasks(true);
+    provider->Start();
+
+    arc_test_.SetUp(&profile_);
+    auto* arc_bridge_service =
+        arc_test_.arc_service_manager()->arc_bridge_service();
+    fake_webapk_instance_ = std::make_unique<arc::FakeWebApkInstance>();
+    arc_bridge_service->webapk()->SetInstance(fake_webapk_instance_.get());
+
+    app_service_test_.FlushMojoCalls();
+
+    test_server_.RegisterRequestHandler(base::BindRepeating(
+        &WebApkInstallTaskTest::HandleWebApkRequest, base::Unretained(this)));
+    ASSERT_TRUE(test_server_.Start());
+
+    GURL server_url = test_server_.GetURL(kServerPath);
+    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+        switches::kWebApkServerUrl, server_url.spec());
+  }
+
+  void SetWebApkResponse(WebApkResponseBuilder builder) {
+    webapk_response_builder_ = builder;
+  }
+
+  bool InstallWebApk(std::string app_id) {
+    bool install_success;
+    apps::WebApkInstallTask install_task(profile(), app_id);
+    base::RunLoop run_loop;
+    install_task.Start(base::BindLambdaForTesting([&](bool success) {
+      install_success = success;
+      run_loop.Quit();
+    }));
+    run_loop.Run();
+    return install_success;
+  }
+
+  std::unique_ptr<net::test_server::HttpResponse> HandleWebApkRequest(
+      const net::test_server::HttpRequest& request) {
+    last_webapk_request_ = std::make_unique<webapk::WebApk>();
+    last_webapk_request_->ParseFromString(request.content);
+    return webapk_response_builder_.Run();
+  }
+
+  TestingProfile* profile() { return &profile_; }
+
+  apps::AppServiceTest* app_service_test() { return &app_service_test_; }
+
+  arc::FakeWebApkInstance* fake_webapk_instance() {
+    return fake_webapk_instance_.get();
+  }
+
+  webapk::WebApk* last_webapk_request() { return last_webapk_request_.get(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+  apps::AppServiceTest app_service_test_;
+  ArcAppTest arc_test_;
+  extensions::ExtensionService* extension_service_ = nullptr;
+
+  net::EmbeddedTestServer test_server_;
+
+  std::unique_ptr<arc::FakeWebApkInstance> fake_webapk_instance_;
+  WebApkResponseBuilder webapk_response_builder_;
+  std::unique_ptr<webapk::WebApk> last_webapk_request_;
+};
+
+TEST_F(WebApkInstallTaskTest, SuccessfulInstall) {
+  auto app_id = web_app::test::InstallWebApp(profile(), BuildWebAppInfo());
+
+  SetWebApkResponse(base::BindRepeating(&BuildValidWebApkResponse,
+                                        "org.chromium.webapk.some_package"));
+
+  EXPECT_TRUE(InstallWebApk(app_id));
+
+  ASSERT_EQ(last_webapk_request()->manifest_url(), kTestManifestUrl);
+  const webapk::WebAppManifest& manifest = last_webapk_request()->manifest();
+  ASSERT_EQ(manifest.short_name(), "Test App");
+  ASSERT_EQ(manifest.start_url(), kTestAppUrl);
+  ASSERT_EQ(manifest.icons(0).src(), kTestAppIcon);
+
+  ASSERT_EQ(fake_webapk_instance()->handled_packages().size(), 1);
+  ASSERT_EQ(fake_webapk_instance()->handled_packages()[0],
+            "org.chromium.webapk.some_package");
+}
+
+TEST_F(WebApkInstallTaskTest, InvalidManifest) {
+  auto app_info = std::make_unique<WebApplicationInfo>();
+  app_info->start_url = GURL(kTestAppUrl);
+  app_info->scope = GURL(kTestAppUrl);
+  app_info->title = kTestAppTitle;
+  app_info->manifest_url = GURL(kTestManifestUrl);
+  auto app_id = web_app::test::InstallWebApp(profile(), std::move(app_info));
+
+  ASSERT_FALSE(InstallWebApk(app_id));
+}
+
+TEST_F(WebApkInstallTaskTest, FailedServerCall) {
+  auto app_id = web_app::test::InstallWebApp(profile(), BuildWebAppInfo());
+
+  SetWebApkResponse(base::BindRepeating(&BuildFailedResponse));
+
+  ASSERT_FALSE(InstallWebApk(app_id));
+
+  ASSERT_EQ(fake_webapk_instance()->handled_packages().size(), 0);
+}
+
+TEST_F(WebApkInstallTaskTest, FailedArcInstall) {
+  auto app_id = web_app::test::InstallWebApp(profile(), BuildWebAppInfo());
+
+  SetWebApkResponse(base::BindRepeating(&BuildValidWebApkResponse,
+                                        "org.chromium.webapk.some_package"));
+  fake_webapk_instance()->set_install_result(
+      arc::mojom::WebApkInstallResult::kErrorResolveNetworkError);
+
+  ASSERT_FALSE(InstallWebApk(app_id));
+  ASSERT_EQ(fake_webapk_instance()->handled_packages()[0],
+            "org.chromium.webapk.some_package");
+}
diff --git a/chrome/browser/apps/app_service/webapk/webapk_manager.cc b/chrome/browser/apps/app_service/webapk/webapk_manager.cc
index 8607b10..af4d0331 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_manager.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_manager.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/webapk/webapk_install_queue.h"
 #include "chrome/browser/ash/apps/apk_web_app_service.h"
 #include "chrome/browser/ash/apps/apk_web_app_service_factory.h"
 #include "chrome/browser/web_applications/components/app_registrar.h"
@@ -26,6 +27,7 @@
   proxy_ = apps::AppServiceProxyFactory::GetForProfile(profile);
   apk_service_ = ash::ApkWebAppServiceFactory::GetForProfile(profile_);
   DCHECK(apk_service_);
+  install_queue_ = std::make_unique<WebApkInstallQueue>(profile);
 
   Observe(&proxy_->AppRegistryCache());
 }
@@ -74,8 +76,7 @@
 }
 
 void WebApkManager::QueueInstall(const apps::AppUpdate& update) {
-  // TODO(crbug.com/1198433): Actually queue the install.
-  VLOG(1) << "Queueing WebAPK install for app: " << update.AppId();
+  install_queue_->Install(update.AppId());
 }
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/webapk/webapk_manager.h b/chrome/browser/apps/app_service/webapk/webapk_manager.h
index 9802f0e..2b1b19b 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_manager.h
+++ b/chrome/browser/apps/app_service/webapk/webapk_manager.h
@@ -18,6 +18,7 @@
 namespace apps {
 
 class AppServiceProxyBase;
+class WebApkInstallQueue;
 
 class WebApkManager : public apps::AppRegistryCache::Observer {
  public:
@@ -41,6 +42,8 @@
   apps::AppServiceProxyBase* proxy_;
   ash::ApkWebAppService* apk_service_;
   web_app::AppRegistrar& web_app_registrar_;
+
+  std::unique_ptr<apps::WebApkInstallQueue> install_queue_;
 };
 
 }  // namespace apps
diff --git a/chrome/browser/ash/apps/apk_web_app_service.cc b/chrome/browser/ash/apps/apk_web_app_service.cc
index 7556a0e..189eb02 100644
--- a/chrome/browser/ash/apps/apk_web_app_service.cc
+++ b/chrome/browser/ash/apps/apk_web_app_service.cc
@@ -57,6 +57,7 @@
 const char kSha256FingerprintKey[] = "sha256_fingerprint";
 constexpr char kLastAppId[] = "last_app_id";
 constexpr char kPinIndex[] = "pin_index";
+constexpr char kGeneratedWebApkPackagePrefix[] = "org.chromium.webapk.";
 
 // Default icon size in pixels to request from ARC for an icon.
 const int kDefaultIconSize = 192;
@@ -251,6 +252,13 @@
   if (!base::FeatureList::IsEnabled(features::kApkWebAppInstalls))
     return;
 
+  // Automatically generated WebAPKs have their lifecycle managed by
+  // WebApkManager and do not need to be considered here.
+  if (base::StartsWith(package_info.package_name,
+                       kGeneratedWebApkPackagePrefix)) {
+    return;
+  }
+
   // This method is called when a) new packages are installed, and b) existing
   // packages are updated. In (b), there are two cases to handle: the package
   // could previously have been an Android app and has now become a web app, and
diff --git a/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc b/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
index 2b9aa66..957f75a1 100644
--- a/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
+++ b/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
@@ -167,6 +167,7 @@
     web_contents->ClosePage();
 
   IntentHandlingMetrics::RecordIntentPickerUserInteractionMetrics(
+      profile,
       /*selected_app_package=*/preferred_app_id.value(),
       GetPickerEntryType(app_type),
       apps::IntentPickerCloseReason::PREFERRED_APP_FOUND,
diff --git a/chrome/browser/ash/apps/metrics/intent_handling_metrics.cc b/chrome/browser/ash/apps/metrics/intent_handling_metrics.cc
index 24abce6..e64993a 100644
--- a/chrome/browser/ash/apps/metrics/intent_handling_metrics.cc
+++ b/chrome/browser/ash/apps/metrics/intent_handling_metrics.cc
@@ -7,6 +7,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/apps/intent_helper/apps_navigation_types.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 
 namespace apps {
 
@@ -29,6 +30,7 @@
 }
 
 void IntentHandlingMetrics::RecordIntentPickerUserInteractionMetrics(
+    content::BrowserContext* context,
     const std::string& selected_app_package,
     PickerEntryType entry_type,
     IntentPickerCloseReason close_reason,
@@ -37,8 +39,8 @@
   if (entry_type == PickerEntryType::kArc &&
       (close_reason == IntentPickerCloseReason::PREFERRED_APP_FOUND ||
        close_reason == IntentPickerCloseReason::OPEN_APP)) {
-    UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
-                              arc::UserInteractionType::APP_STARTED_FROM_LINK);
+    arc::ArcMetricsService::RecordArcUserInteraction(
+        context, arc::UserInteractionType::APP_STARTED_FROM_LINK);
   }
   PickerAction action =
       GetPickerAction(entry_type, close_reason, should_persist);
diff --git a/chrome/browser/ash/apps/metrics/intent_handling_metrics.h b/chrome/browser/ash/apps/metrics/intent_handling_metrics.h
index bd79ea3..e3a898a 100644
--- a/chrome/browser/ash/apps/metrics/intent_handling_metrics.h
+++ b/chrome/browser/ash/apps/metrics/intent_handling_metrics.h
@@ -13,6 +13,10 @@
 #include "chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
 
+namespace content {
+class BrowserContext;
+}  // namespace content
+
 namespace apps {
 
 class IntentHandlingMetrics {
@@ -98,6 +102,7 @@
                                         Platform platform);
 
   static void RecordIntentPickerUserInteractionMetrics(
+      content::BrowserContext* context,
       const std::string& selected_app_package,
       PickerEntryType entry_type,
       IntentPickerCloseReason close_reason,
diff --git a/chrome/browser/ash/apps/metrics/intent_handling_metrics_unittest.cc b/chrome/browser/ash/apps/metrics/intent_handling_metrics_unittest.cc
index 1e4a7d3..33a21e4 100644
--- a/chrome/browser/ash/apps/metrics/intent_handling_metrics_unittest.cc
+++ b/chrome/browser/ash/apps/metrics/intent_handling_metrics_unittest.cc
@@ -8,14 +8,19 @@
 #include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/arc/arc_prefs.h"
+#include "components/arc/arc_service_manager.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
+#include "components/arc/metrics/arc_metrics_service.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
+#include "components/prefs/testing_pref_service.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace apps {
 
-typedef testing::Test IntentHandlingMetricsTest;
-
-TEST_F(IntentHandlingMetricsTest, TestRecordIntentPickerMetrics) {
+TEST(IntentHandlingMetricsTest, TestRecordIntentPickerMetrics) {
   base::HistogramTester histogram_tester;
 
   IntentHandlingMetrics test = IntentHandlingMetrics();
@@ -41,14 +46,39 @@
       IntentHandlingMetrics::Platform::ARC, 1);
 }
 
-TEST_F(IntentHandlingMetricsTest,
+// A fixture class that sets up arc::ArcMetricsService.
+class IntentHandlingMetricsTestWithMetricsService : public testing::Test {
+ protected:
+  IntentHandlingMetricsTestWithMetricsService() = default;
+  IntentHandlingMetricsTestWithMetricsService(
+      const IntentHandlingMetricsTestWithMetricsService&) = delete;
+  IntentHandlingMetricsTestWithMetricsService& operator=(
+      const IntentHandlingMetricsTestWithMetricsService&) = delete;
+  ~IntentHandlingMetricsTestWithMetricsService() override = default;
+
+  void SetUp() override {
+    arc::prefs::RegisterLocalStatePrefs(local_state_.registry());
+    arc::StabilityMetricsManager::Initialize(&local_state_);
+    arc::ArcMetricsService::GetForBrowserContextForTesting(profile());
+  }
+
+  Profile* profile() { return &profile_; }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  arc::ArcServiceManager arc_service_manager_;
+  TestingPrefServiceSimple local_state_;
+  TestingProfile profile_;
+};
+
+TEST_F(IntentHandlingMetricsTestWithMetricsService,
        TestRecordIntentPickerUserInteractionMetrics) {
   base::HistogramTester histogram_tester;
 
   IntentHandlingMetrics test = IntentHandlingMetrics();
   test.RecordIntentPickerUserInteractionMetrics(
-      "app_package", PickerEntryType::kArc, IntentPickerCloseReason::OPEN_APP,
-      Source::kExternalProtocol, true);
+      profile(), "app_package", PickerEntryType::kArc,
+      IntentPickerCloseReason::OPEN_APP, Source::kExternalProtocol, true);
 
   histogram_tester.ExpectBucketCount(
       "Arc.UserInteraction", arc::UserInteractionType::APP_STARTED_FROM_LINK,
@@ -59,7 +89,7 @@
       IntentHandlingMetrics::PickerAction::ARC_APP_PREFERRED_PRESSED, 1);
 }
 
-TEST_F(IntentHandlingMetricsTest, TestRecordExternalProtocolMetrics) {
+TEST(IntentHandlingMetricsTest, TestRecordExternalProtocolMetrics) {
   base::HistogramTester histogram_tester;
 
   IntentHandlingMetrics test = IntentHandlingMetrics();
@@ -71,7 +101,7 @@
       arc::ProtocolAction::IRC_ACCEPTED_PERSISTED, 1);
 }
 
-TEST_F(IntentHandlingMetricsTest, TestRecordOpenBrowserMetrics) {
+TEST(IntentHandlingMetricsTest, TestRecordOpenBrowserMetrics) {
   base::HistogramTester histogram_tester;
 
   IntentHandlingMetrics test;
diff --git a/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc b/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
index e87e9cf11..84941ff 100644
--- a/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
+++ b/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
@@ -492,8 +492,8 @@
     apps::IntentHandlingMetrics::RecordExternalProtocolMetrics(
         Scheme::TEL, entry_type, /*accepted=*/true, should_persist);
     apps::IntentHandlingMetrics::RecordIntentPickerUserInteractionMetrics(
-        selected_app_package, entry_type, reason,
-        apps::Source::kExternalProtocol, should_persist);
+        web_contents->GetBrowserContext(), selected_app_package, entry_type,
+        reason, apps::Source::kExternalProtocol, should_persist);
     return;
   }
 
@@ -587,8 +587,8 @@
       url_scheme, entry_type, protocol_accepted, should_persist);
 
   apps::IntentHandlingMetrics::RecordIntentPickerUserInteractionMetrics(
-      selected_app_package, entry_type, reason, apps::Source::kExternalProtocol,
-      should_persist);
+      web_contents->GetBrowserContext(), selected_app_package, entry_type,
+      reason, apps::Source::kExternalProtocol, should_persist);
 }
 
 // Called when ARC returned activity icons for the |handlers|.
@@ -678,9 +678,9 @@
 
   WebContents* web_contents =
       tab_util::GetWebContentsByID(render_process_host_id, routing_id);
+  auto* context = web_contents->GetBrowserContext();
   auto* intent_helper_bridge =
-      web_contents ? ArcIntentHelperBridge::GetForBrowserContext(
-                         web_contents->GetBrowserContext())
+      web_contents ? ArcIntentHelperBridge::GetForBrowserContext(context)
                    : nullptr;
 
   // We only reach here if Chrome doesn't think it can handle the URL. If ARC is
@@ -700,7 +700,7 @@
                 handlers.size(), &result, safe_to_bypass_ui)) {
     if (result == GetActionResult::HANDLE_URL_IN_ARC) {
       apps::IntentHandlingMetrics::RecordIntentPickerUserInteractionMetrics(
-          std::string(), apps::PickerEntryType::kArc,
+          context, std::string(), apps::PickerEntryType::kArc,
           apps::IntentPickerCloseReason::PREFERRED_APP_FOUND,
           apps::Source::kExternalProtocol,
           /*should_persist=*/false);
diff --git a/chrome/browser/autofill/strike_database_factory.cc b/chrome/browser/autofill/strike_database_factory.cc
index 0ccc46e..86554d9 100644
--- a/chrome/browser/autofill/strike_database_factory.cc
+++ b/chrome/browser/autofill/strike_database_factory.cc
@@ -7,7 +7,7 @@
 #include "base/memory/singleton.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/storage_partition.h"
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index c7b4aa2..fad2a34 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -82,8 +82,8 @@
 #include "chrome/common/buildflags.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 61b6ba3..b79841d9 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -71,9 +71,9 @@
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/test_autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/bookmarks/browser/bookmark_model.h"
diff --git a/chrome/browser/chromeos/file_manager/arc_file_tasks.cc b/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
index a36bebcaf..e3bd3cd 100644
--- a/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
@@ -31,6 +31,7 @@
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "components/arc/intent_helper/intent_constants.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/file_system.mojom.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
@@ -306,9 +307,8 @@
   std::move(done).Run(
       extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT, "");
 
-  UMA_HISTOGRAM_ENUMERATION(
-      "Arc.UserInteraction",
-      arc::UserInteractionType::APP_STARTED_FROM_FILE_MANAGER);
+  arc::ArcMetricsService::RecordArcUserInteraction(
+      profile, arc::UserInteractionType::APP_STARTED_FROM_FILE_MANAGER);
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/note_taking_helper.cc b/chrome/browser/chromeos/note_taking_helper.cc
index 62d5ba7..db9d0f94 100644
--- a/chrome/browser/chromeos/note_taking_helper.cc
+++ b/chrome/browser/chromeos/note_taking_helper.cc
@@ -39,6 +39,7 @@
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/file_system.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/prefs/pref_service.h"
@@ -690,9 +691,8 @@
     arc_file_system->OpenUrlsWithPermission(std::move(request),
                                             base::DoNothing());
 
-    UMA_HISTOGRAM_ENUMERATION(
-        "Arc.UserInteraction",
-        arc::UserInteractionType::APP_STARTED_FROM_STYLUS_TOOLS);
+    arc::ArcMetricsService::RecordArcUserInteraction(
+        profile, arc::UserInteractionType::APP_STARTED_FROM_STYLUS_TOOLS);
 
     return LaunchResult::ANDROID_SUCCESS;
   }
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
index 18d6f6f..cf8ac8a 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
@@ -220,8 +220,8 @@
 // Fan test values:
 constexpr uint32_t kFakeSpeedRpm = 1225;
 // Stateful partition test values:
-constexpr uint64_t kAvailableSpace = 777;
-constexpr uint64_t kTotalSpace = 999;
+constexpr int64_t kAvailableSpace = 777;
+constexpr int64_t kTotalSpace = 999;
 constexpr char kFilesystem[] = "ext4";
 constexpr char kMountSource[] = "/dev/mmcblk0p1";
 // Bluetooth test values:
diff --git a/chrome/browser/downgrade/snapshot_file_collector.cc b/chrome/browser/downgrade/snapshot_file_collector.cc
index 39206bcdd..304ae3d0 100644
--- a/chrome/browser/downgrade/snapshot_file_collector.cc
+++ b/chrome/browser/downgrade/snapshot_file_collector.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/common/chrome_constants.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/bookmarks/common/bookmark_constants.h"
 #include "components/history/core/browser/history_constants.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index 717d339..99644ee 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -82,6 +82,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/download/public/common/download_danger_type.h"
+#include "components/download/public/common/download_features.h"
 #include "components/download/public/common/download_interrupt_reasons.h"
 #include "components/download/public/common/download_item.h"
 #include "components/download/public/common/in_progress_download_manager.h"
@@ -4635,6 +4636,10 @@
 class InProgressDownloadTest : public DownloadTest {
  public:
   InProgressDownloadTest() {
+    feature_list_.InitWithFeatures(
+        {download::features::kUseInProgressDownloadManagerForDownloadService},
+        {});
+
     // The in progress download manager will be released from
     // `DownloadManagerUtils` during creation of the `DownloadManagerImpl`. As
     // the `DownloadManagerImpl` may be created before test bodies can run,
@@ -4677,6 +4682,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   download::InProgressDownloadManager* in_progress_manager_ = nullptr;
 };
 
@@ -4753,7 +4759,6 @@
 
 // Check that InProgressDownloadManager can handle transient downloads with the
 // same GUID.
-// TODO(crbug.com/1204298): Disabled due to flakines.
 IN_PROC_BROWSER_TEST_F(InProgressDownloadTest,
                        DownloadURLWithInProgressManager) {
   embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index b2aef76..8cbccc0 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -1902,9 +1902,11 @@
         if (!data->json().HasKey(iter.key()) ||
             (data->json().Get(iter.key(), &old_value) &&
              !iter.value().Equals(old_value))) {
-          delta->Set(iter.key() + ".current", iter.value().CreateDeepCopy());
+          delta->Set(iter.key() + ".current",
+                     base::Value::ToUniquePtrValue(iter.value().Clone()));
           if (old_value)
-            delta->Set(iter.key() + ".previous", old_value->CreateDeepCopy());
+            delta->Set(iter.key() + ".previous",
+                       base::Value::ToUniquePtrValue(old_value->Clone()));
           changed = true;
         }
       }
@@ -1918,7 +1920,8 @@
           IsDownloadDeltaField(iter.key())) {
         // estimatedEndTime disappears after completion, but bytesReceived
         // stays.
-        delta->Set(iter.key() + ".previous", iter.value().CreateDeepCopy());
+        delta->Set(iter.key() + ".previous",
+                   base::Value::ToUniquePtrValue(iter.value().Clone()));
         changed = true;
       }
     }
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 31be0ca0..3208054 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -30,9 +30,12 @@
 #include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/dbus/userdataauth/cryptohome_misc_client.h"
 #include "chromeos/dbus/userdataauth/userdataauth_client.h"
+#include "chromeos/network/cellular_esim_profile_handler_impl.h"
+#include "chromeos/network/cellular_metrics_logger.h"
 #include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_certificate_handler.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_handler_test_helper.h"
 #include "chromeos/network/network_metadata_store.h"
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
@@ -77,7 +80,6 @@
 using testing::Return;
 using testing::_;
 
-using chromeos::DBusThreadManager;
 using chromeos::ShillDeviceClient;
 using chromeos::ShillIPConfigClient;
 using chromeos::ShillManagerClient;
@@ -144,11 +146,7 @@
 
 class NetworkingPrivateChromeOSApiTest : public extensions::ExtensionApiTest {
  public:
-  NetworkingPrivateChromeOSApiTest()
-      : manager_test_(nullptr),
-        profile_test_(nullptr),
-        service_test_(nullptr),
-        device_test_(nullptr) {}
+  NetworkingPrivateChromeOSApiTest() {}
 
   bool RunNetworkingSubtest(const std::string& test) {
     const std::string arg =
@@ -212,8 +210,8 @@
     UIDelegateStub::s_show_account_details_called_ = 0;
 
     // Add a Cellular GSM Device.
-    device_test_->AddDevice(kCellularDevicePath, shill::kTypeCellular,
-                            "stub_cellular_device1");
+    device_test()->AddDevice(kCellularDevicePath, shill::kTypeCellular,
+                             "stub_cellular_device1");
     base::DictionaryValue home_provider;
     home_provider.SetString("name", "Cellular1_Provider");
     home_provider.SetString("code", "000000");
@@ -236,24 +234,24 @@
                       base::Value("test_min"));
     SetDeviceProperty(kCellularDevicePath, shill::kModelIdProperty,
                       base::Value("test_model_id"));
-    device_test_->SetSimLocked(kCellularDevicePath, false);
+    device_test()->SetSimLocked(kCellularDevicePath, false);
 
     // Add the Cellular Service.
     AddService(kCellular1ServicePath, "cellular1", shill::kTypeCellular,
                shill::kStateIdle);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kCellular1ServicePath, shill::kAutoConnectProperty, base::Value(true));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kCellular1ServicePath, shill::kNetworkTechnologyProperty,
         base::Value(shill::kNetworkTechnologyGsm));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kCellular1ServicePath, shill::kActivationStateProperty,
         base::Value(shill::kActivationStateNotActivated));
-    service_test_->SetServiceProperty(kCellular1ServicePath,
-                                      shill::kRoamingStateProperty,
-                                      base::Value(shill::kRoamingStateHome));
+    service_test()->SetServiceProperty(kCellular1ServicePath,
+                                       shill::kRoamingStateProperty,
+                                       base::Value(shill::kRoamingStateHome));
 
-    profile_test_->AddService(kUser1ProfilePath, kCellular1ServicePath);
+    profile_test()->AddService(kUser1ProfilePath, kCellular1ServicePath);
     content::RunAllPendingInMessageLoop();
   }
 
@@ -276,15 +274,15 @@
                   const std::string& name,
                   const std::string& type,
                   const std::string& state) {
-    service_test_->AddService(service_path, service_path + "_guid", name, type,
-                              state, true /* add_to_visible */);
+    service_test()->AddService(service_path, service_path + "_guid", name, type,
+                               state, true /* add_to_visible */);
   }
 
   void SetDeviceProperty(const std::string& device_path,
                          const std::string& name,
                          const base::Value& value) {
-    device_test_->SetDeviceProperty(device_path, name, value,
-                                    /*notify_changed=*/true);
+    device_test()->SetDeviceProperty(device_path, name, value,
+                                     /*notify_changed=*/true);
   }
 
   static std::unique_ptr<KeyedService> CreateNetworkingPrivateDelegate(
@@ -306,20 +304,13 @@
 
     InitializeSanitizedUsername();
 
-    DBusThreadManager* dbus_manager = DBusThreadManager::Get();
-    manager_test_ = dbus_manager->GetShillManagerClient()->GetTestInterface();
-    profile_test_ = dbus_manager->GetShillProfileClient()->GetTestInterface();
-    service_test_ = dbus_manager->GetShillServiceClient()->GetTestInterface();
-    device_test_ = dbus_manager->GetShillDeviceClient()->GetTestInterface();
-
-    ShillIPConfigClient::TestInterface* ip_config_test =
-        dbus_manager->GetShillIPConfigClient()->GetTestInterface();
-
-    device_test_->ClearDevices();
-    service_test_->ClearServices();
+    network_handler_test_helper_ =
+        std::make_unique<chromeos::NetworkHandlerTestHelper>();
+    device_test()->ClearDevices();
+    service_test()->ClearServices();
 
     // Sends a notification about the added profile.
-    profile_test_->AddProfile(kUser1ProfilePath, userhash_);
+    profile_test()->AddProfile(kUser1ProfilePath, userhash_);
 
     // Add IPConfigs
     base::DictionaryValue ipconfig;
@@ -327,14 +318,15 @@
     ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.1"));
     ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(0));
     ipconfig.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv4));
-    ip_config_test->AddIPConfig(kIPConfigPath, ipconfig);
+    network_handler_test_helper_->ip_config_test()->AddIPConfig(kIPConfigPath,
+                                                                ipconfig);
 
     // Add Devices
-    device_test_->AddDevice(kEthernetDevicePath, shill::kTypeEthernet,
-                            "stub_ethernet_device1");
+    device_test()->AddDevice(kEthernetDevicePath, shill::kTypeEthernet,
+                             "stub_ethernet_device1");
 
-    device_test_->AddDevice(kWifiDevicePath, shill::kTypeWifi,
-                            "stub_wifi_device1");
+    device_test()->AddDevice(kWifiDevicePath, shill::kTypeWifi,
+                             "stub_wifi_device1");
     base::ListValue wifi_ip_configs;
     wifi_ip_configs.AppendString(kIPConfigPath);
     SetDeviceProperty(kWifiDevicePath, shill::kIPConfigsProperty,
@@ -345,84 +337,90 @@
     // Add Services
     AddService("stub_ethernet", "eth0", shill::kTypeEthernet,
                shill::kStateOnline);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         "stub_ethernet", shill::kProfileProperty,
         base::Value(ShillProfileClient::GetSharedProfilePath()));
-    profile_test_->AddService(ShillProfileClient::GetSharedProfilePath(),
-                              "stub_ethernet");
+    profile_test()->AddService(ShillProfileClient::GetSharedProfilePath(),
+                               "stub_ethernet");
 
     AddService(kWifi1ServicePath, "wifi1", shill::kTypeWifi,
                shill::kStateOnline);
-    service_test_->SetServiceProperty(kWifi1ServicePath,
-                                      shill::kSecurityClassProperty,
-                                      base::Value(shill::kSecurityWep));
-    service_test_->SetServiceProperty(kWifi1ServicePath, shill::kWifiBSsid,
-                                      base::Value("00:01:02:03:04:05"));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(kWifi1ServicePath,
+                                       shill::kSecurityClassProperty,
+                                       base::Value(shill::kSecurityWep));
+    service_test()->SetServiceProperty(kWifi1ServicePath, shill::kWifiBSsid,
+                                       base::Value("00:01:02:03:04:05"));
+    service_test()->SetServiceProperty(
         kWifi1ServicePath, shill::kSignalStrengthProperty, base::Value(40));
-    service_test_->SetServiceProperty(kWifi1ServicePath,
-                                      shill::kProfileProperty,
-                                      base::Value(kUser1ProfilePath));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(kWifi1ServicePath,
+                                       shill::kProfileProperty,
+                                       base::Value(kUser1ProfilePath));
+    service_test()->SetServiceProperty(
         kWifi1ServicePath, shill::kConnectableProperty, base::Value(true));
-    service_test_->SetServiceProperty(kWifi1ServicePath, shill::kDeviceProperty,
-                                      base::Value(kWifiDevicePath));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(kWifi1ServicePath,
+                                       shill::kDeviceProperty,
+                                       base::Value(kWifiDevicePath));
+    service_test()->SetServiceProperty(
         kWifi1ServicePath, shill::kTetheringProperty,
         base::Value(shill::kTetheringNotDetectedState));
     base::DictionaryValue static_ipconfig;
     static_ipconfig.SetKey(shill::kAddressProperty, base::Value("1.2.3.4"));
     static_ipconfig.SetKey(shill::kGatewayProperty, base::Value("0.0.0.0"));
     static_ipconfig.SetKey(shill::kPrefixlenProperty, base::Value(1));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kWifi1ServicePath, shill::kStaticIPConfigProperty, static_ipconfig);
     base::ListValue frequencies1;
     frequencies1.AppendInteger(2400);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kWifi1ServicePath, shill::kWifiFrequencyListProperty, frequencies1);
-    service_test_->SetServiceProperty(kWifi1ServicePath, shill::kWifiFrequency,
-                                      base::Value(2400));
-    profile_test_->AddService(kUser1ProfilePath, kWifi1ServicePath);
+    service_test()->SetServiceProperty(kWifi1ServicePath, shill::kWifiFrequency,
+                                       base::Value(2400));
+    profile_test()->AddService(kUser1ProfilePath, kWifi1ServicePath);
 
     AddService(kWifi2ServicePath, "wifi2_PSK", shill::kTypeWifi,
                shill::kStateIdle);
-    service_test_->SetServiceProperty(kWifi2ServicePath,
-                                      shill::kSecurityClassProperty,
-                                      base::Value(shill::kSecurityPsk));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(kWifi2ServicePath,
+                                       shill::kSecurityClassProperty,
+                                       base::Value(shill::kSecurityPsk));
+    service_test()->SetServiceProperty(
         kWifi2ServicePath, shill::kSignalStrengthProperty, base::Value(80));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kWifi2ServicePath, shill::kConnectableProperty, base::Value(true));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kWifi2ServicePath, shill::kTetheringProperty,
         base::Value(shill::kTetheringNotDetectedState));
 
     base::ListValue frequencies2;
     frequencies2.AppendInteger(2400);
     frequencies2.AppendInteger(5000);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kWifi2ServicePath, shill::kWifiFrequencyListProperty, frequencies2);
-    service_test_->SetServiceProperty(kWifi2ServicePath, shill::kWifiFrequency,
-                                      base::Value(5000));
-    service_test_->SetServiceProperty(kWifi2ServicePath,
-                                      shill::kProfileProperty,
-                                      base::Value(kUser1ProfilePath));
-    profile_test_->AddService(kUser1ProfilePath, kWifi2ServicePath);
+    service_test()->SetServiceProperty(kWifi2ServicePath, shill::kWifiFrequency,
+                                       base::Value(5000));
+    service_test()->SetServiceProperty(kWifi2ServicePath,
+                                       shill::kProfileProperty,
+                                       base::Value(kUser1ProfilePath));
+    profile_test()->AddService(kUser1ProfilePath, kWifi2ServicePath);
 
     AddService("stub_vpn1", "vpn1", shill::kTypeVPN, shill::kStateOnline);
-    service_test_->SetServiceProperty("stub_vpn1", shill::kProviderTypeProperty,
-                                      base::Value(shill::kProviderOpenVpn));
-    profile_test_->AddService(kUser1ProfilePath, "stub_vpn1");
+    service_test()->SetServiceProperty("stub_vpn1",
+                                       shill::kProviderTypeProperty,
+                                       base::Value(shill::kProviderOpenVpn));
+    profile_test()->AddService(kUser1ProfilePath, "stub_vpn1");
 
     AddService("stub_vpn2", "vpn2", shill::kTypeVPN, shill::kStateOffline);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         "stub_vpn2", shill::kProviderTypeProperty,
         base::Value(shill::kProviderThirdPartyVpn));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         "stub_vpn2", shill::kProviderHostProperty,
         base::Value("third_party_provider_extension_id"));
-    profile_test_->AddService(kUser1ProfilePath, "stub_vpn2");
+    profile_test()->AddService(kUser1ProfilePath, "stub_vpn2");
 
+    chromeos::CellularMetricsLogger::RegisterLocalStatePrefs(
+        local_state_.registry());
+    chromeos::CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
+        local_state_.registry());
     PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
     PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry());
     ::onc::RegisterProfilePrefs(user_prefs_.registry());
@@ -435,11 +433,24 @@
     content::RunAllPendingInMessageLoop();
   }
 
+  void TearDownOnMainThread() { network_handler_test_helper_.reset(); }
+
+  ShillServiceClient::TestInterface* service_test() {
+    return network_handler_test_helper_->service_test();
+  }
+  ShillProfileClient::TestInterface* profile_test() {
+    return network_handler_test_helper_->profile_test();
+  }
+  ShillDeviceClient::TestInterface* device_test() {
+    return network_handler_test_helper_->device_test();
+  }
+  ShillManagerClient::TestInterface* manager_test() {
+    return network_handler_test_helper_->manager_test();
+  }
+
  protected:
-  ShillManagerClient::TestInterface* manager_test_;
-  ShillProfileClient::TestInterface* profile_test_;
-  ShillServiceClient::TestInterface* service_test_;
-  ShillDeviceClient::TestInterface* device_test_;
+  std::unique_ptr<chromeos::NetworkHandlerTestHelper>
+      network_handler_test_helper_;
   testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
   sync_preferences::TestingPrefServiceSyncable user_prefs_;
   TestingPrefServiceSimple local_state_;
@@ -485,8 +496,8 @@
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetNetworks) {
   // Hide stub_wifi2.
-  service_test_->SetServiceProperty(kWifi2ServicePath, shill::kVisibleProperty,
-                                    base::Value(false));
+  service_test()->SetServiceProperty(kWifi2ServicePath, shill::kVisibleProperty,
+                                     base::Value(false));
   // Add a couple of additional networks that are not configured (saved).
   AddService("stub_wifi3", "wifi3", shill::kTypeWifi, shill::kStateIdle);
   AddService("stub_wifi4", "wifi4", shill::kTypeWifi, shill::kStateIdle);
@@ -510,9 +521,9 @@
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetDeviceStates) {
   SetupCellular();
-  manager_test_->RemoveTechnology("cellular");
-  manager_test_->AddTechnology("cellular", false /* disabled */);
-  manager_test_->SetTechnologyInitializing("cellular", true);
+  manager_test()->RemoveTechnology("cellular");
+  manager_test()->AddTechnology("cellular", false /* disabled */);
+  manager_test()->SetTechnologyInitializing("cellular", true);
   EXPECT_TRUE(RunNetworkingSubtest("getDeviceStates")) << message_;
 }
 
@@ -638,13 +649,13 @@
           "WiFi": {"Passphrase": "FAKE_CREDENTIAL_VPaJDV9x"}
         }
       })";
-  service_test_->SetServiceProperty(kWifi2ServicePath, shill::kUIDataProperty,
-                                    base::Value(kUidataBlob));
-  service_test_->SetServiceProperty(
+  service_test()->SetServiceProperty(kWifi2ServicePath, shill::kUIDataProperty,
+                                     base::Value(kUidataBlob));
+  service_test()->SetServiceProperty(
       kWifi2ServicePath, shill::kAutoConnectProperty, base::Value(false));
 
   // Update the profile entry.
-  profile_test_->AddService(kUser1ProfilePath, kWifi2ServicePath);
+  profile_test()->AddService(kUser1ProfilePath, kWifi2ServicePath);
 
   content::RunAllPendingInMessageLoop();
 
@@ -730,10 +741,10 @@
                        GetCaptivePortalStatus) {
   // Ethernet defaults to online. Set wifi1 to idle -> 'Offline', and wifi2 to
   // redirect-found -> 'Portal'.
-  service_test_->SetServiceProperty(kWifi1ServicePath, shill::kStateProperty,
-                                    base::Value(shill::kStateIdle));
-  service_test_->SetServiceProperty(kWifi2ServicePath, shill::kStateProperty,
-                                    base::Value(shill::kStateRedirectFound));
+  service_test()->SetServiceProperty(kWifi1ServicePath, shill::kStateProperty,
+                                     base::Value(shill::kStateIdle));
+  service_test()->SetServiceProperty(kWifi2ServicePath, shill::kStateProperty,
+                                     base::Value(shill::kStateRedirectFound));
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(RunNetworkingSubtest("getCaptivePortalStatus")) << message_;
@@ -743,11 +754,11 @@
                        CaptivePortalNotification) {
   // Make wifi1 the default service since captive portal notifications only
   // occur for the default service.
-  service_test_->RemoveService("stub_ethernet");
-  service_test_->RemoveService("stub_vpn1");
+  service_test()->RemoveService("stub_ethernet");
+  service_test()->RemoveService("stub_vpn1");
   TestListener listener("notifyPortalDetectorObservers",
                         base::BindLambdaForTesting([&]() {
-                          service_test_->SetServiceProperty(
+                          service_test()->SetServiceProperty(
                               kWifi1ServicePath, shill::kStateProperty,
                               base::Value(shill::kStateRedirectFound));
                         }));
@@ -757,7 +768,7 @@
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, UnlockCellularSim) {
   SetupCellular();
   // Lock the SIM
-  device_test_->SetSimLocked(kCellularDevicePath, true);
+  device_test()->SetSimLocked(kCellularDevicePath, true);
   EXPECT_TRUE(RunNetworkingSubtest("unlockCellularSim")) << message_;
 }
 
@@ -791,7 +802,7 @@
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, CellularSimPuk) {
   SetupCellular();
   // Lock the SIM
-  device_test_->SetSimLocked(kCellularDevicePath, true);
+  device_test()->SetSimLocked(kCellularDevicePath, true);
   EXPECT_TRUE(RunNetworkingSubtest("cellularSimPuk")) << message_;
 }
 
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
index 780c4abf..baf89d6d 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
@@ -263,8 +263,8 @@
                          WebNavigationApiTestWithContextType,
                          testing::Values(ContextType::kServiceWorker));
 
-IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, ClientRedirect) {
-  ASSERT_TRUE(RunExtensionTest("webnavigation/clientRedirect")) << message_;
+IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, ClientRedirect) {
+  ASSERT_TRUE(RunTest("webnavigation/clientRedirect")) << message_;
 }
 
 IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, ServerRedirect) {
@@ -349,8 +349,8 @@
   ASSERT_TRUE(RunExtensionTest("webnavigation/simpleLoad")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, Failures) {
-  ASSERT_TRUE(RunExtensionTest("webnavigation/failures")) << message_;
+IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, Failures) {
+  ASSERT_TRUE(RunTest("webnavigation/failures")) << message_;
 }
 
 IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, FilteredTest) {
@@ -574,12 +574,12 @@
   ASSERT_TRUE(RunExtensionTest("webnavigation/pendingDeletion")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, Crash) {
+IN_PROC_BROWSER_TEST_P(WebNavigationApiTestWithContextType, Crash) {
   content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   // Wait for the extension to set itself up and return control to us.
-  ASSERT_TRUE(RunExtensionTest("webnavigation/crash")) << message_;
+  ASSERT_TRUE(RunTest("webnavigation/crash")) << message_;
 
   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
   EXPECT_TRUE(content::WaitForLoadStop(tab));
diff --git a/chrome/browser/favicon/content_favicon_driver_browsertest.cc b/chrome/browser/favicon/content_favicon_driver_browsertest.cc
index 48d9486..cf1f894 100644
--- a/chrome/browser/favicon/content_favicon_driver_browsertest.cc
+++ b/chrome/browser/favicon/content_favicon_driver_browsertest.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/favicon/core/favicon_handler.h"
@@ -36,10 +37,13 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
+#include "content/public/test/prerender_test_util.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "net/base/load_flags.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "net/url_request/url_request.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/common/features.h"
@@ -51,6 +55,18 @@
 
 using testing::ElementsAre;
 
+std::unique_ptr<net::test_server::HttpResponse> NoContentResponseHandler(
+    const std::string& path,
+    const net::test_server::HttpRequest& request) {
+  if (path != request.relative_url)
+    return nullptr;
+
+  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+      new net::test_server::BasicHttpResponse);
+  http_response->set_code(net::HTTP_NO_CONTENT);
+  return std::move(http_response);
+}
+
 // Tracks which URLs are loaded and whether the requests bypass the cache.
 class TestURLLoaderInterceptor {
  public:
@@ -214,11 +230,15 @@
 
 class ContentFaviconDriverTest : public InProcessBrowserTest {
  public:
-  ContentFaviconDriverTest() {}
-  ~ContentFaviconDriverTest() override {}
+  ContentFaviconDriverTest()
+      : prerender_helper_(
+            base::BindRepeating(&ContentFaviconDriverTest::web_contents,
+                                base::Unretained(this))) {}
+  ~ContentFaviconDriverTest() override = default;
 
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
+    prerender_helper_.SetUpOnMainThread(embedded_test_server());
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -267,10 +287,162 @@
     return GetFaviconForPageURL(url, icon_type, /*desired_size_in_dip=*/0);
   }
 
+  content::test::PrerenderTestHelper& prerender_helper() {
+    return prerender_helper_;
+  }
+
  private:
+  content::test::PrerenderTestHelper prerender_helper_;
+
   DISALLOW_COPY_AND_ASSIGN(ContentFaviconDriverTest);
 };
 
+IN_PROC_BROWSER_TEST_F(ContentFaviconDriverTest,
+                       DoNotLoadFaviconsWhilePrerendering) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL prerender_url =
+      embedded_test_server()->GetURL("/favicon/page_with_favicon.html");
+  GURL icon_url = embedded_test_server()->GetURL("/favicon/icon.png");
+  GURL initial_url =
+      embedded_test_server()->GetURL("/prerender/add_prerender.html");
+  prerender_helper().NavigatePrimaryPage(initial_url);
+
+  {
+    PendingTaskWaiter waiter(web_contents());
+    prerender_helper().AddPrerender(prerender_url);
+    waiter.Wait();
+  }
+
+  // We should not fetch the URL while prerendering.
+  prerender_helper().WaitForRequest(prerender_url, 1);
+  EXPECT_EQ(prerender_helper().GetRequestCount(icon_url), 0);
+  prerender_helper().NavigatePrimaryPage(prerender_url);
+
+  // Check that we've fetched the URL upon activation. Should not hang.
+  EXPECT_EQ(prerender_helper().GetRequestCount(prerender_url), 1);
+  prerender_helper().WaitForRequest(icon_url, 1);
+}
+
+class NoCommittedEntryWebContentsObserver
+    : public content::WebContentsObserver {
+ public:
+  explicit NoCommittedEntryWebContentsObserver(
+      content::WebContents* web_contents) {
+    Observe(web_contents);
+  }
+
+  ~NoCommittedEntryWebContentsObserver() override = default;
+
+  bool DidUpdateFaviconURLWithNoCommittedEntry() const {
+    return did_update_favicon_url_with_no_committed_entry_;
+  }
+
+ protected:
+  // WebContentsObserver:
+  void DidUpdateFaviconURL(
+      content::RenderFrameHost* rfh,
+      const std::vector<blink::mojom::FaviconURLPtr>& candidates) override {
+    auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
+    if (!web_contents->GetController().GetLastCommittedEntry()) {
+      did_update_favicon_url_with_no_committed_entry_ = true;
+    }
+  }
+
+ private:
+  bool did_update_favicon_url_with_no_committed_entry_ = false;
+};
+
+// Observes the creation of new tabs and, upon creation, sets up both a pending
+// task waiter (to ensure that ContentFaviconDriver tasks complete) and a
+// NoCommittedEntryWebContentsObserver (to ensure that we observe the expected
+// function calls).
+class FaviconUpdateNoLastCommittedEntryTabStripObserver
+    : public TabStripModelObserver {
+ public:
+  explicit FaviconUpdateNoLastCommittedEntryTabStripObserver(
+      TabStripModel* model)
+      : model_(model) {
+    model_->AddObserver(this);
+  }
+  ~FaviconUpdateNoLastCommittedEntryTabStripObserver() override {
+    model_->RemoveObserver(this);
+  }
+
+  void WaitForNewTab() {
+    if (!pending_task_waiter_)
+      run_loop_.Run();
+  }
+
+  bool DidUpdateFaviconURLWithNoCommittedEntry() const {
+    return observer_->DidUpdateFaviconURLWithNoCommittedEntry();
+  }
+
+  PendingTaskWaiter* pending_task_waiter() {
+    return pending_task_waiter_.get();
+  }
+
+ protected:
+  // TabStripModelObserver:
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override {
+    if (change.type() != TabStripModelChange::kInserted)
+      return;
+    auto* web_contents = model_->GetActiveWebContents();
+    pending_task_waiter_ = std::make_unique<PendingTaskWaiter>(web_contents);
+    observer_ =
+        std::make_unique<NoCommittedEntryWebContentsObserver>(web_contents);
+    run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  TabStripModel* model_ = nullptr;
+  std::unique_ptr<PendingTaskWaiter> pending_task_waiter_;
+  std::unique_ptr<NoCommittedEntryWebContentsObserver> observer_;
+};
+
+// Tests that ContentFaviconDriver can handle being sent updated favicon URLs
+// if there is no last committed entry. This occurs when script is injected in
+// about:blank in a newly created window. See crbug.com/520759 for more
+// details.
+IN_PROC_BROWSER_TEST_F(ContentFaviconDriverTest,
+                       FaviconUpdateNoLastCommittedEntry) {
+  const char kNoContentPath[] = "/nocontent";
+  embedded_test_server()->RegisterRequestHandler(
+      base::BindRepeating(&NoContentResponseHandler, kNoContentPath));
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL empty_url = embedded_test_server()->GetURL("/empty.html");
+  GURL no_content_url = embedded_test_server()->GetURL("/nocontent");
+
+  FaviconUpdateNoLastCommittedEntryTabStripObserver observer(
+      browser()->tab_strip_model());
+
+  auto* rfh = ui_test_utils::NavigateToURLWithDisposition(
+      browser(), empty_url, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+
+  EXPECT_TRUE(content::ExecJs(rfh, content::JsReplace(R"(
+        let w = window.open();
+        w.document.write('abc');
+        w.document.close();
+        w.location.href = $1;)",
+                                                      no_content_url)));
+
+  // Ensure that we have created our tab and set up the pending task waiter and
+  // web contents observer.
+  observer.WaitForNewTab();
+
+  // Wait for WebContentsObsever::DidUpdateFaviconURL() call and for any
+  // subsequent ContentFaviconDriver tasks to finish.
+  observer.pending_task_waiter()->Wait();
+
+  // We expect DidUpdateFaviconURL to be called and for no crash to ensue.
+  EXPECT_TRUE(observer.DidUpdateFaviconURLWithNoCommittedEntry());
+}
+
 // Test that when a user reloads a page ignoring the cache that the favicon is
 // is redownloaded and (not returned from either the favicon cache or the HTTP
 // cache).
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 154c0a6..b0b5c82 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1446,7 +1446,7 @@
   {
     "name": "enable-autofill-upi-vpa",
     "owners": [ "cfroussios" ],
-    "expiry_milestone": 88
+    "expiry_milestone": 98
   },
   {
     "name": "enable-background-blur",
@@ -4318,6 +4318,17 @@
     "expiry_milestone": 92
   },
   {
+    "name": "privacy-sandbox-settings-2",
+    "owners": [
+      "andzaytsev",
+      "harrisonsean",
+      "sauski",
+      "rainhard",
+      "msramek",
+      "chrome-friendly-settings@google.com"],
+    "expiry_milestone": 94
+  },
+  {
     "name": "prominent-dark-mode-active-tab-title",
     "owners": [ "dfried" ],
     "expiry_milestone": 82
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 94ac969b7..41ff5b4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2063,6 +2063,11 @@
     "Enables privacy sandbox settings. Requires at least one of the Privacy "
     "Sandbox APIs to be enabled.";
 
+const char kPrivacySandboxSettings2Name[] = "Privacy Sandbox Settings 2";
+const char kPrivacySandboxSettings2Description[] =
+    "Enables the second set of privacy sandbox settings. Requires "
+    "#privacy-sandbox-settings to also be enabled";
+
 const char kSafetyCheckWeakPasswordsName[] = "Safety check for weak passwords";
 const char kSafetyCheckWeakPasswordsDescription[] =
     "If weak passwords were found, show them in safety check.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index c49f135..bd60cc7 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1188,6 +1188,9 @@
 extern const char kPrivacySandboxSettingsName[];
 extern const char kPrivacySandboxSettingsDescription[];
 
+extern const char kPrivacySandboxSettings2Name[];
+extern const char kPrivacySandboxSettings2Description[];
+
 extern const char kSafetyCheckWeakPasswordsName[];
 extern const char kSafetyCheckWeakPasswordsDescription[];
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity.cc b/chrome/browser/media/router/providers/cast/cast_activity.cc
index 27657e3..1f0f28f5 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity.cc
@@ -69,7 +69,7 @@
            << session_id_.value_or("<missing>")
            << ", new session_id = " << session.session_id();
   DCHECK(sink.is_cast_sink());
-  route_.set_description(session.GetRouteDescription());
+  route_.set_description(GetRouteDescription(session));
   sink_ = sink;
   if (session_id_) {
     DCHECK_EQ(*session_id_, session.session_id());
@@ -178,6 +178,11 @@
 void CastActivity::OnSessionUpdated(const CastSession& session,
                                     const std::string& hash_token) {}
 
+std::string CastActivity::GetRouteDescription(
+    const CastSession& session) const {
+  return session.GetRouteDescription();
+}
+
 CastSessionClientFactoryForTest* CastActivity::client_factory_for_test_ =
     nullptr;
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity.h b/chrome/browser/media/router/providers/cast/cast_activity.h
index a242826..4c53545 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity.h
@@ -165,6 +165,8 @@
     return it == connected_clients_.end() ? nullptr : it->second.get();
   }
 
+  virtual std::string GetRouteDescription(const CastSession& session) const;
+
   int cast_channel_id() const { return sink_.cast_channel_id(); }
 
   MediaRoute route_;
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.cc b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
index 7b8bfa84..40ecf85 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
@@ -18,10 +18,12 @@
 #include "base/optional.h"
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/browser/media/router/data_decoder_util.h"
 #include "chrome/browser/media/router/providers/cast/cast_activity_manager.h"
 #include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/cast_channel/cast_message_util.h"
 #include "components/cast_channel/cast_socket.h"
 #include "components/cast_channel/enum_table.h"
@@ -34,6 +36,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/ip_address.h"
 #include "third_party/openscreen/src/cast/common/channel/proto/cast_channel.pb.h"
+#include "ui/base/l10n/l10n_util.h"
 
 using blink::mojom::PresentationConnectionMessagePtr;
 using cast_channel::Result;
@@ -319,6 +322,23 @@
     mojo::PendingReceiver<mojom::MediaController> media_controller,
     mojo::PendingRemote<mojom::MediaStatusObserver> observer) {}
 
+std::string MirroringActivity::GetRouteDescription(
+    const CastSession& session) const {
+  if (!mirroring_type_) {
+    return CastActivity::GetRouteDescription(session);
+  }
+  switch (*mirroring_type_) {
+    case MirroringActivity::MirroringType::kTab:
+      return l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_CASTING_TAB);
+    case MirroringActivity::MirroringType::kDesktop:
+      return l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_CASTING_DESKTOP);
+    case MirroringActivity::MirroringType::kOffscreenTab:
+      return l10n_util::GetStringFUTF8(
+          IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION,
+          base::UTF8ToUTF16(route().media_source().url().host()));
+  }
+}
+
 void MirroringActivity::HandleParseJsonResult(
     const std::string& route_id,
     data_decoder::DataDecoder::ValueOrError result) {
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.h b/chrome/browser/media/router/providers/cast/mirroring_activity.h
index ca1c8fb4..8c0b281 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.h
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.h
@@ -72,6 +72,7 @@
   void CreateMediaController(
       mojo::PendingReceiver<mojom::MediaController> media_controller,
       mojo::PendingRemote<mojom::MediaStatusObserver> observer) override;
+  std::string GetRouteDescription(const CastSession& session) const override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(MirroringActivityTest, GetScrubbedLogMessage);
diff --git a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
index 200e046..507732c1 100644
--- a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
@@ -73,7 +73,7 @@
 std::string WiredDisplayMediaRouteProvider::GetRouteDescription(
     const std::string& media_source) {
   return l10n_util::GetStringFUTF8(
-      IDS_MEDIA_ROUTER_WIRED_DISPLAY_ROUTE_DESCRIPTION,
+      IDS_MEDIA_ROUTER_PRESENTATION_ROUTE_DESCRIPTION,
       base::UTF8ToUTF16(url::Origin::Create(GURL(media_source)).host()));
 }
 
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
index 504f3a2..65f46ff4 100644
--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
+++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
@@ -147,7 +147,16 @@
         // "Cancel" button in auto fetch offline page notification.
         int AUTO_FETCH_CANCEL = 16;
 
-        int NUM_ENTRIES = 17;
+        // Media notification buttons.
+        int MEDIA_ACTION_PLAY = 17;
+        int MEDIA_ACTION_PAUSE = 18;
+        int MEDIA_ACTION_STOP = 19;
+        int MEDIA_ACTION_PREVIOUS_TRACK = 20;
+        int MEDIA_ACTION_NEXT_TRACK = 21;
+        int MEDIA_ACTION_SEEK_FORWARD = 22;
+        int MEDIA_ACTION_SEEK_BACKWARD = 23;
+
+        int NUM_ENTRIES = 24;
     }
 
     private static class LazyHolder {
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/BiometricAuthenticatorBridge.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/BiometricAuthenticatorBridge.java
index 558e5b1..962b15f 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/BiometricAuthenticatorBridge.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/BiometricAuthenticatorBridge.java
@@ -16,19 +16,24 @@
 import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
 
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.compat.ApiHelperForQ;
 import org.chromium.ui.base.WindowAndroid;
 
 class BiometricAuthenticatorBridge {
     private final Context mContext;
+    private long mNativeBiometricAuthenticator;
 
-    private BiometricAuthenticatorBridge(WindowAndroid windowAndroid) {
+    private BiometricAuthenticatorBridge(
+            long nativeBiometricAuthenticator, WindowAndroid windowAndroid) {
         mContext = windowAndroid.getApplicationContext();
+        mNativeBiometricAuthenticator = nativeBiometricAuthenticator;
     }
 
     @CalledByNative
-    private static BiometricAuthenticatorBridge create(WindowAndroid windowAndroid) {
-        return new BiometricAuthenticatorBridge(windowAndroid);
+    private static BiometricAuthenticatorBridge create(
+            long nativeBiometricAuthenticator, WindowAndroid windowAndroid) {
+        return new BiometricAuthenticatorBridge(nativeBiometricAuthenticator, windowAndroid);
     }
 
     @CalledByNative
@@ -58,4 +63,28 @@
             }
         }
     }
+
+    @CalledByNative
+    void authenticate() {
+        // TODO(crbug.com/1031483): Trigger a biometric prompt.
+        onAuthenticationCompleted(true);
+    }
+
+    @CalledByNative
+    void cancel() {
+        mNativeBiometricAuthenticator = 0;
+        // TODO(crbug.com/1031483): Cancel the reauth if one is in progress.
+    }
+
+    void onAuthenticationCompleted(boolean success) {
+        if (mNativeBiometricAuthenticator != 0) {
+            BiometricAuthenticatorBridgeJni.get().onAuthenticationCompleted(
+                    mNativeBiometricAuthenticator, success);
+        }
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onAuthenticationCompleted(long nativeBiometricAuthenticatorAndroid, boolean success);
+    }
 }
diff --git a/chrome/browser/password_manager/biometric_authenticator_android.cc b/chrome/browser/password_manager/biometric_authenticator_android.cc
index 0bb835b..85afc6a 100644
--- a/chrome/browser/password_manager/biometric_authenticator_android.cc
+++ b/chrome/browser/password_manager/biometric_authenticator_android.cc
@@ -10,6 +10,7 @@
 #include "base/android/jni_android.h"
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "base/location.h"
 #include "chrome/browser/password_manager/android/jni_headers/BiometricAuthenticatorBridge_jni.h"
@@ -28,7 +29,7 @@
 using password_manager::UiCredential;
 
 // static
-std::unique_ptr<ChromeBiometricAuthenticator>
+scoped_refptr<password_manager::BiometricAuthenticator>
 ChromeBiometricAuthenticator::Create(WebContents* web_contents) {
   if (!base::FeatureList::IsEnabled(
           password_manager::features::kBiometricTouchToFill)) {
@@ -41,13 +42,15 @@
     return nullptr;
   }
 
-  return std::make_unique<BiometricAuthenticatorAndroid>(window_android);
+  return base::WrapRefCounted(
+      new BiometricAuthenticatorAndroid(window_android));
 }
 
 BiometricAuthenticatorAndroid::BiometricAuthenticatorAndroid(
     ui::WindowAndroid* window_android) {
   java_object_ = Java_BiometricAuthenticatorBridge_create(
-      AttachCurrentThread(), window_android->GetJavaObject());
+      AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
+      window_android->GetJavaObject());
 }
 
 BiometricAuthenticatorAndroid::~BiometricAuthenticatorAndroid() = default;
@@ -61,7 +64,20 @@
 void BiometricAuthenticatorAndroid::Authenticate(
     const UiCredential& credential,
     AuthenticateCallback callback) {
-  // TODO(crbug.com/1031483): Implement.
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), true));
+  callback_ = std::move(callback);
+  Java_BiometricAuthenticatorBridge_authenticate(AttachCurrentThread(),
+                                                 java_object_);
+}
+
+void BiometricAuthenticatorAndroid::Cancel() {
+  callback_.Reset();
+  Java_BiometricAuthenticatorBridge_cancel(AttachCurrentThread(), java_object_);
+}
+
+void BiometricAuthenticatorAndroid::OnAuthenticationCompleted(
+    JNIEnv* env,
+    jboolean success) {
+  if (callback_.is_null())
+    return;
+  std::move(callback_).Run(success);
 }
diff --git a/chrome/browser/password_manager/biometric_authenticator_android.h b/chrome/browser/password_manager/biometric_authenticator_android.h
index 66c0415..b252216 100644
--- a/chrome/browser/password_manager/biometric_authenticator_android.h
+++ b/chrome/browser/password_manager/biometric_authenticator_android.h
@@ -15,13 +15,29 @@
 class BiometricAuthenticatorAndroid : public ChromeBiometricAuthenticator {
  public:
   explicit BiometricAuthenticatorAndroid(ui::WindowAndroid* window_android);
-  ~BiometricAuthenticatorAndroid() override;
 
+  // Checks whether biometrics are available.
   password_manager::BiometricsAvailability CanAuthenticate() override;
+
+  // Trigges an authentication flow based on biometrics, with the
+  // screen lock as fallback.
   void Authenticate(const password_manager::UiCredential& credential,
                     AuthenticateCallback callback) override;
 
+  // Should be called by the object using the authenticator if the purpose
+  // for which the auth was requested becomes obsolete or the object is
+  // destroyed.
+  void Cancel() override;
+
+  // Called by Java when the authentication completes.
+  void OnAuthenticationCompleted(JNIEnv* env, jboolean success);
+
  private:
+  ~BiometricAuthenticatorAndroid() override;
+
+  // Callback to be executed after the authentication completes.
+  AuthenticateCallback callback_;
+
   // This object is an instance of BiometricAuthenticatorBridge, i.e. the Java
   // counterpart to this class.
   base::android::ScopedJavaGlobalRef<jobject> java_object_;
diff --git a/chrome/browser/password_manager/chrome_biometric_authenticator.h b/chrome/browser/password_manager/chrome_biometric_authenticator.h
index b7f797d..7905ee3 100644
--- a/chrome/browser/password_manager/chrome_biometric_authenticator.h
+++ b/chrome/browser/password_manager/chrome_biometric_authenticator.h
@@ -22,8 +22,11 @@
   // Create an instance of the ChromeBiometricAuthenticator. Trying to use this
   // API on platforms that do not provide an implementation will result in a
   // link error. So far only Android provides an implementation.
-  static std::unique_ptr<ChromeBiometricAuthenticator> Create(
+  static scoped_refptr<password_manager::BiometricAuthenticator> Create(
       content::WebContents* web_contents);
+
+ protected:
+  ~ChromeBiometricAuthenticator() override = default;
 };
 
 #endif  // CHROME_BROWSER_PASSWORD_MANAGER_CHROME_BIOMETRIC_AUTHENTICATOR_H_
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 8b6bd41..a04983d9 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -448,7 +448,7 @@
                                            autofill_assistant::UIState::kShown;
 }
 
-password_manager::BiometricAuthenticator*
+scoped_refptr<password_manager::BiometricAuthenticator>
 ChromePasswordManagerClient::GetBiometricAuthenticator() {
 #if defined(OS_ANDROID)
   if (!biometric_authenticator_) {
@@ -456,7 +456,7 @@
         ChromeBiometricAuthenticator::Create(web_contents());
   }
 #endif
-  return biometric_authenticator_.get();
+  return biometric_authenticator_;
 }
 
 void ChromePasswordManagerClient::GeneratePassword(
@@ -1143,9 +1143,10 @@
 
 TouchToFillController*
 ChromePasswordManagerClient::GetOrCreateTouchToFillController() {
-  if (!touch_to_fill_controller_)
-    touch_to_fill_controller_ = std::make_unique<TouchToFillController>(this);
-
+  if (!touch_to_fill_controller_) {
+    touch_to_fill_controller_ = std::make_unique<TouchToFillController>(
+        this, GetBiometricAuthenticator());
+  }
   return touch_to_fill_controller_.get();
 }
 #endif  // defined(OS_ANDROID)
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index 21b22abc..f79a72f2 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -12,6 +12,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -60,7 +61,6 @@
 #include "components/password_manager/core/browser/sync_credentials_filter.h"
 #endif
 
-class ChromeBiometricAuthenticator;
 class PasswordGenerationPopupObserver;
 class PasswordGenerationPopupControllerImpl;
 class Profile;
@@ -126,8 +126,8 @@
   // Returns a pointer to the BiometricAuthenticator which is created on demand.
   // This is currently only implemented for Android, on all other platforms this
   // will always be null.
-  password_manager::BiometricAuthenticator* GetBiometricAuthenticator()
-      override;
+  scoped_refptr<password_manager::BiometricAuthenticator>
+  GetBiometricAuthenticator() override;
   void GeneratePassword(
       autofill::password_generation::PasswordGenerationType type) override;
   void NotifyUserAutoSignin(
@@ -368,7 +368,8 @@
       generated_password_saved_message_delegate_;
 #endif  // defined(OS_ANDROID)
 
-  std::unique_ptr<ChromeBiometricAuthenticator> biometric_authenticator_;
+  scoped_refptr<password_manager::BiometricAuthenticator>
+      biometric_authenticator_;
 
   password_manager::ContentPasswordManagerDriverFactory* driver_factory_;
 
diff --git a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_css.html b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_css.html
index 742c3e2..fd278328a 100644
--- a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_css.html
+++ b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_css.html
@@ -64,6 +64,7 @@
 
       /* Layout for the new OOBE buttons is adaptive */
       .new-oobe-buttons-layout {
+        align-items: center;
         display: flex;
         justify-content: flex-end;
         padding-inline-end: 32px;
diff --git a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.html b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.html
index 0e57b82a..c41e908 100644
--- a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.html
+++ b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.html
@@ -15,7 +15,7 @@
       <slot name="main"></slot>
     </div>
   </div>
-  <div class="footer">
+  <div class="footer" hidden$="[[!showButtonFooter_]]">
     <slot name="buttons"></slot>
   </div>
 </div>
diff --git a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.js b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.js
index 0786fd3..7aab30c1 100644
--- a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.js
+++ b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_template.js
@@ -11,4 +11,24 @@
   _template: html`{__html_template__}`,
 
   behaviors: [CrScrollableBehavior],
+
+  properties: {
+    /**
+     * Indicates whether the footer/button div should be shown.
+     * @private {boolean}
+     */
+    showButtonFooter_: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  /**
+   * Shows/hides the button footer.
+   * @param {boolean} show Whether to show the footer.
+   */
+  showButtonFooter(show) {
+    this.showButtonFooter_ = show;
+  },
+
 });
diff --git a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_ui.js b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_ui.js
index b7dd508..93a2017 100644
--- a/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_ui.js
+++ b/chrome/browser/resources/chromeos/edu_coexistence/edu_coexistence_ui.js
@@ -126,8 +126,8 @@
    */
   configureUiForGaiaFlow() {
     var currentUrl = new URL(this.webview_.src);
-    var contentContainer =
-        this.$$('edu-coexistence-template').$$('div.content-container');
+    var template = this.$$('edu-coexistence-template');
+    var contentContainer = template.$$('div.content-container');
 
     if (currentUrl.hostname !== this.controller_.getFlowOriginHostname()) {
       this.$$('edu-coexistence-button').newOobeStyleEnabled =
@@ -139,12 +139,11 @@
       this.showGaiaButtons_ = true;
       // Shrink the content-container so that the buttons line up more closely
       // with the server rendered buttons.
-      contentContainer.style.height = 'calc(100% - 245px)';
+      contentContainer.style.height = 'calc(100% - 90px)';
 
       // Don't show the "Next" button if the EDU authentication got forwarded to
       // a non-Google SSO page.
       this.showGaiaNextButton_ = currentUrl.hostname.endsWith('.google.com');
-
     } else {
       // Hide the GAIA Buttons.
       this.showGaiaButtons_ = false;
@@ -155,6 +154,8 @@
       // Restore the content container div to 100%
       contentContainer.style.height = '100%';
     }
+
+    template.showButtonFooter(this.showGaiaButtons_);
   },
 
   /** @override */
diff --git a/chrome/browser/resources/chromeos/login/arc_terms_of_service.js b/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
index b5bda8e..f1cc26d2 100644
--- a/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
+++ b/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
@@ -394,8 +394,8 @@
    * @param {boolean} managed Defines whether this setting is set by policy.
    */
   setLocationServicesMode(enabled, managed) {
-    this.backupRestore = enabled;
-    this.backupRestoreManaged = managed;
+    this.locationService = enabled;
+    this.locationServiceManaged = managed;
   },
 
   /**
diff --git a/chrome/browser/resources/discards/graph_doc.js b/chrome/browser/resources/discards/graph_doc.js
index 242d2ae..ae6f0b1 100644
--- a/chrome/browser/resources/discards/graph_doc.js
+++ b/chrome/browser/resources/discards/graph_doc.js
@@ -396,10 +396,14 @@
 
   /** override */
   get dashedLinkTargets() {
-    if (this.page.embedderFrameId) {
-      return [this.page.embedderFrameId];
+    const targets = [];
+    if (this.page.openerFrameId) {
+      targets.push(this.page.openerFrameId);
     }
-    return [];
+    if (this.page.embedderFrameId) {
+      targets.push(this.page.embedderFrameId);
+    }
+    return targets;
   }
 }
 
diff --git a/chrome/browser/resources/read_later/app.html b/chrome/browser/resources/read_later/app.html
index a19924f3..794a6eb 100644
--- a/chrome/browser/resources/read_later/app.html
+++ b/chrome/browser/resources/read_later/app.html
@@ -5,11 +5,20 @@
     justify-content: flex-end;
   }
 
+  :host([side-panel]) #top-container {
+    display: none;
+  }
+
   #readLaterList {
     max-height: 500px;
     overflow: auto;
   }
 
+  :host([side-panel]) #readLaterList {
+    margin-block-start: 8px;
+    max-height: none;
+  }
+
   #header {
     align-items: center;
     color: var(--cr-primary-text-color);
@@ -51,6 +60,19 @@
     margin-top: 4px;
   }
 
+  :host([side-panel]) .sub-heading {
+    border: none;
+    font-weight: 600;
+    height: 20px;
+    margin: 0;
+    padding: 8px 16px;
+  }
+
+  :host([side-panel]) #ureadItemsList + .sub-heading {
+    border-top: 1px solid #dbdbdb;
+    margin-block-start: 8px;
+  }
+
   #empty-state-header {
     color: var(--cr-primary-text-color);
     font-size: 15px;
diff --git a/chrome/browser/resources/read_later/side_panel/app.html b/chrome/browser/resources/read_later/side_panel/app.html
index e02c723..b2714b4 100644
--- a/chrome/browser/resources/read_later/side_panel/app.html
+++ b/chrome/browser/resources/read_later/side_panel/app.html
@@ -26,6 +26,6 @@
 </header>
 
 <iron-pages selected="[[selectedTab_]]">
-  <read-later-app></read-later-app>
+  <read-later-app side-panel></read-later-app>
   <div><!-- Bookmarks! --></div>
 </iron-pages>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
index 85b2e1d..ef1b0a00 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
@@ -16,6 +16,10 @@
         --cr-dialog-width: 324px;
       }
 
+      #title {
+        height: 15px;
+      }
+
       #warningMessage {
         --iron-icon-fill-color: #5F6368;
         --iron-icon-height: 16px;
@@ -33,7 +37,9 @@
       }
     </style>
     <cr-dialog id="dialog" show-on-attach>
-      <div slot="title">[[getTitleString_(esimProfileName_)]]</div>
+      <div id="title" slot="title">
+        [[getTitleString_(esimProfileName_)]]
+      </div>
       <div slot="body">
         <div>$i18n{eSimRemoveProfileDialogDescription}</div>
         <div id="warningMessage" hidden$="[[!showCellularDisconnectWarning]]">
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
index 308308b..3b84273f 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
@@ -74,6 +74,9 @@
    * @private
    */
   getTitleString_() {
+    if (!this.esimProfileName_) {
+      return '';
+    }
     return this.i18n('esimRemoveProfileDialogTitle', this.esimProfileName_);
   },
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
index 33fd2a9..18a8013 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
@@ -9,7 +9,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/profiles/profile_keep_alive_types.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/scoped_profile_keep_alive.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/mock_chrome_cleaner_controller_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
@@ -124,9 +126,10 @@
 }
 
 IN_PROC_BROWSER_TEST_P(ChromeCleanerPromptUserTest, AllBrowsersClosed) {
-  std::unique_ptr<ScopedKeepAlive> keep_alive =
-      std::make_unique<ScopedKeepAlive>(KeepAliveOrigin::BROWSER,
-                                        KeepAliveRestartOption::DISABLED);
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED);
+  auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
+      browser()->profile(), ProfileKeepAliveOrigin::kBrowserWindow);
 
   CloseAllBrowsers();
   base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/safe_browsing/incident_reporting/state_store.cc b/chrome/browser/safe_browsing/incident_reporting/state_store.cc
index 4179d36..91369be6 100644
--- a/chrome/browser/safe_browsing/incident_reporting/state_store.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/state_store.cc
@@ -133,7 +133,7 @@
   if (value_dict) {
     if (value_dict->empty())
       transaction.ClearAll();
-    else if (!incidents_sent_ || !incidents_sent_->Equals(value_dict.get()))
+    else if (!incidents_sent_ || *incidents_sent_ != *value_dict)
       transaction.ReplacePrefDict(std::move(value_dict));
   }
 
diff --git a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
index cf3ff3f..7f3c78a1 100644
--- a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
+++ b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
@@ -2224,7 +2224,7 @@
   // Try to prerender the page with an invalid certificate.
   auto prerender_url = test_server->GetURL("/title1.html");
   content::test::PrerenderHostRegistryObserver registry_observer(
-      web_contents());
+      *web_contents());
   content::NavigationHandleObserver nav_observer(web_contents(), prerender_url);
   prerender_helper_.AddPrerenderAsync(prerender_url);
 
@@ -2232,7 +2232,7 @@
   registry_observer.WaitForTrigger(prerender_url);
   auto prerender_id = prerender_helper_.GetHostForUrl(prerender_url);
   EXPECT_NE(content::RenderFrameHost::kNoFrameTreeNodeId, prerender_id);
-  content::test::PrerenderHostObserver host_observer(web_contents(),
+  content::test::PrerenderHostObserver host_observer(*web_contents(),
                                                      prerender_id);
 
   // Since the prerender has not yet activated, the state should still be
@@ -2285,7 +2285,7 @@
   prerender_helper_.AddPrerender(prerender_url);
   auto prerender_id = prerender_helper_.GetHostForUrl(prerender_url);
   EXPECT_NE(content::RenderFrameHost::kNoFrameTreeNodeId, prerender_id);
-  content::test::PrerenderHostObserver host_observer(web_contents(),
+  content::test::PrerenderHostObserver host_observer(*web_contents(),
                                                      prerender_id);
 
   // Since the prerender has not yet activated, the state should still be
diff --git a/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc b/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
index 53383aa..5157f44 100644
--- a/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
@@ -1001,14 +1001,8 @@
   base::test::ScopedFeatureList features_;
 };
 
-// TODO(1185289): Fails under msan.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_ShouldNotDeleteLastClosedTab DISABLED_ShouldNotDeleteLastClosedTab
-#else
-#define MAYBE_ShouldNotDeleteLastClosedTab ShouldNotDeleteLastClosedTab
-#endif
 IN_PROC_BROWSER_TEST_F(SingleClientSessionsWithDestroyProfileSyncTest,
-                       MAYBE_ShouldNotDeleteLastClosedTab) {
+                       ShouldNotDeleteLastClosedTab) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(CheckInitialState(0));
 
diff --git a/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc b/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
index e4534cd..baeb699 100644
--- a/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
@@ -269,14 +269,8 @@
   base::test::ScopedFeatureList features_;
 };
 
-// TODO(1185289): Fails under msan.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_ShouldNotSyncLastClosedTab DISABLED_ShouldNotSyncLastClosedTab
-#else
-#define MAYBE_ShouldNotSyncLastClosedTab ShouldNotSyncLastClosedTab
-#endif
 IN_PROC_BROWSER_TEST_F(TwoClientSessionsWithDestroyProfileSyncTest,
-                       MAYBE_ShouldNotSyncLastClosedTab) {
+                       ShouldNotSyncLastClosedTab) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   ASSERT_TRUE(CheckInitialState(0));
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
index 7d69cf1..57b9df3f 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
@@ -6,13 +6,18 @@
 
 #include <utility>
 
+#include "base/callback_helpers.h"
 #include "base/check.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
 #include "base/types/pass_key.h"
+#include "chrome/browser/password_manager/biometric_authenticator_android.h"
+#include "chrome/browser/password_manager/chrome_biometric_authenticator.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
+#include "chrome/browser/touch_to_fill/touch_to_fill_view_factory.h"
 #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/biometric_authenticator.h"
 #include "components/password_manager/core/browser/origin_credential_store.h"
 #include "components/password_manager/core/browser/password_manager_driver.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
@@ -28,6 +33,7 @@
 
 using ShowVirtualKeyboard =
     password_manager::PasswordManagerDriver::ShowVirtualKeyboard;
+using password_manager::BiometricsAvailability;
 using password_manager::PasswordManagerDriver;
 using password_manager::UiCredential;
 
@@ -49,15 +55,23 @@
 }  // namespace
 
 TouchToFillController::TouchToFillController(
-    base::PassKey<TouchToFillControllerTest>) {}
+    base::PassKey<TouchToFillControllerTest>,
+    scoped_refptr<password_manager::BiometricAuthenticator> authenticator)
+    : authenticator_(std::move(authenticator)) {}
 
 TouchToFillController::TouchToFillController(
-    ChromePasswordManagerClient* password_client)
+    ChromePasswordManagerClient* password_client,
+    scoped_refptr<password_manager::BiometricAuthenticator> authenticator)
     : password_client_(password_client),
+      authenticator_(std::move(authenticator)),
       source_id_(ukm::GetSourceIdForWebContentsDocument(
           password_client_->web_contents())) {}
 
-TouchToFillController::~TouchToFillController() = default;
+TouchToFillController::~TouchToFillController() {
+  if (auth_in_progress_) {
+    authenticator_->Cancel();
+  }
+}
 
 void TouchToFillController::Show(base::span<const UiCredential> credentials,
                                  base::WeakPtr<PasswordManagerDriver> driver) {
@@ -92,15 +106,24 @@
   if (!driver_)
     return;
 
-  password_manager::metrics_util::LogFilledCredentialIsFromAndroidApp(
-      credential.is_affiliation_based_match().value());
-  driver_->TouchToFillClosed(ShowVirtualKeyboard(false));
-  std::exchange(driver_, nullptr)
-      ->FillSuggestion(credential.username(), credential.password());
-
   ukm::builders::TouchToFill_Shown(source_id_)
       .SetUserAction(static_cast<int64_t>(UserAction::kSelectedCredential))
       .Record(ukm::UkmRecorder::Get());
+
+  if (!authenticator_ ||
+      authenticator_->CanAuthenticate() !=
+          password_manager::BiometricsAvailability::kAvailable) {
+    FillCredential(credential);
+    return;
+  }
+
+  auth_in_progress_ = true;
+  // `this` notifies the authenticator when it is destructed, resulting in
+  // the callback being reset by the authenticator. Therefore, it is safe
+  // to use base::Unretained.
+  authenticator_->Authenticate(
+      credential, base::BindOnce(&TouchToFillController::OnReauthCompleted,
+                                 base::Unretained(this), credential));
 }
 
 void TouchToFillController::OnManagePasswordsSelected() {
@@ -133,3 +156,29 @@
 gfx::NativeView TouchToFillController::GetNativeView() {
   return password_client_->web_contents()->GetNativeView();
 }
+
+void TouchToFillController::OnReauthCompleted(UiCredential credential,
+                                              bool authSuccessful) {
+  auth_in_progress_ = false;
+  if (!driver_)
+    return;
+
+  if (!authSuccessful) {
+    std::exchange(driver_, nullptr)
+        ->TouchToFillClosed(ShowVirtualKeyboard(true));
+    return;
+  }
+
+  FillCredential(credential);
+}
+
+void TouchToFillController::FillCredential(const UiCredential& credential) {
+  DCHECK(driver_);
+
+  password_manager::metrics_util::LogFilledCredentialIsFromAndroidApp(
+      credential.is_affiliation_based_match().value());
+  driver_->TouchToFillClosed(ShowVirtualKeyboard(false));
+
+  std::exchange(driver_, nullptr)
+      ->FillSuggestion(credential.username(), credential.password());
+}
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller.h b/chrome/browser/touch_to_fill/touch_to_fill_controller.h
index 28cdb65..4123038 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller.h
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller.h
@@ -10,10 +10,12 @@
 #include <vector>
 
 #include "base/containers/span.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/types/pass_key.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view_factory.h"
+#include "components/password_manager/core/browser/biometric_authenticator.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -41,9 +43,12 @@
   };
 
   // No-op constructor for tests.
-  explicit TouchToFillController(
-      base::PassKey<class TouchToFillControllerTest>);
-  explicit TouchToFillController(ChromePasswordManagerClient* web_contents);
+  TouchToFillController(
+      base::PassKey<class TouchToFillControllerTest>,
+      scoped_refptr<password_manager::BiometricAuthenticator> authenticator);
+  TouchToFillController(
+      ChromePasswordManagerClient* password_client,
+      scoped_refptr<password_manager::BiometricAuthenticator> authenticator);
   TouchToFillController(const TouchToFillController&) = delete;
   TouchToFillController& operator=(const TouchToFillController&) = delete;
   ~TouchToFillController();
@@ -75,6 +80,14 @@
 #endif
 
  private:
+  // Called after the biometric reauth completes. If `authSuccessful` is
+  // true, `credential` will be filled into the form.
+  void OnReauthCompleted(password_manager::UiCredential credential,
+                         bool authSuccessful);
+
+  // Fills the credential into the form.
+  void FillCredential(const password_manager::UiCredential& credential);
+
   // Weak pointer to the ChromePasswordManagerClient this class is tied to.
   ChromePasswordManagerClient* password_client_ = nullptr;
 
@@ -82,6 +95,13 @@
   // OnCredentialSelected() or OnDismissed() gets called.
   base::WeakPtr<password_manager::PasswordManagerDriver> driver_;
 
+  // Authenticator used to trigger a biometric auth before filling.
+  scoped_refptr<password_manager::BiometricAuthenticator> authenticator_;
+
+  // True while an authentication is in progress. Used to check whether there
+  // is an auth to cancel when this object is destroyed.
+  bool auth_in_progress_ = false;
+
   ukm::SourceId source_id_ = ukm::kInvalidSourceId;
 
   // View used to communicate with the Android frontend. Lazily instantiated so
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
index ac1e750..9282645 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
@@ -9,10 +9,14 @@
 
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/types/pass_key.h"
+#include "components/password_manager/core/browser/biometric_authenticator.h"
+#include "components/password_manager/core/browser/mock_biometric_authenticator.h"
 #include "components/password_manager/core/browser/origin_credential_store.h"
+#include "components/password_manager/core/browser/stub_password_manager_client.h"
 #include "components/password_manager/core/browser/stub_password_manager_driver.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -25,10 +29,13 @@
 
 using ShowVirtualKeyboard =
     password_manager::PasswordManagerDriver::ShowVirtualKeyboard;
+using base::test::RunOnceCallback;
+using password_manager::MockBiometricAuthenticator;
 using password_manager::UiCredential;
 using ::testing::_;
 using ::testing::ElementsAreArray;
 using ::testing::Eq;
+using ::testing::Return;
 using ::testing::ReturnRefOfCopy;
 using ::testing::WithArg;
 using IsOriginSecure = TouchToFillView::IsOriginSecure;
@@ -87,10 +94,22 @@
         .WillByDefault(ReturnRefOfCopy(GURL(kExampleCom)));
   }
 
+  std::unique_ptr<TouchToFillController> CreateNoAuthController() {
+    return std::make_unique<TouchToFillController>(
+        base::PassKey<TouchToFillControllerTest>(), nullptr);
+  }
+
+  std::unique_ptr<TouchToFillController> CreateAuthController() {
+    return std::make_unique<TouchToFillController>(
+        base::PassKey<TouchToFillControllerTest>(), authenticator_);
+  }
+
   MockPasswordManagerDriver& driver() { return driver_; }
 
   MockTouchToFillView& view() { return *mock_view_; }
 
+  MockBiometricAuthenticator* authenticator() { return authenticator_.get(); }
+
   ukm::TestAutoSetUkmRecorder& test_recorder() { return test_recorder_; }
 
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
@@ -103,14 +122,49 @@
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   MockTouchToFillView* mock_view_ = nullptr;
+  scoped_refptr<MockBiometricAuthenticator> authenticator_ =
+      base::MakeRefCounted<MockBiometricAuthenticator>();
   MockPasswordManagerDriver driver_;
   base::HistogramTester histogram_tester_;
   ukm::TestAutoSetUkmRecorder test_recorder_;
   TouchToFillController touch_to_fill_controller_{
-      base::PassKey<TouchToFillControllerTest>()};
+      base::PassKey<TouchToFillControllerTest>(), authenticator_};
 };
 
-TEST_F(TouchToFillControllerTest, Show_And_Fill) {
+TEST_F(TouchToFillControllerTest, Show_And_Fill_No_Auth) {
+  std::unique_ptr<TouchToFillController> controller_no_auth =
+      CreateNoAuthController();
+  std::unique_ptr<MockTouchToFillView> mock_view =
+      std::make_unique<MockTouchToFillView>();
+  MockTouchToFillView* weak_view = mock_view.get();
+  controller_no_auth->set_view(std::move(mock_view));
+
+  UiCredential credentials[] = {
+      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
+
+  EXPECT_CALL(*weak_view, Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
+                               ElementsAreArray(credentials)));
+  controller_no_auth->Show(credentials, driver().AsWeakPtr());
+
+  // Test that we correctly log the absence of an Android credential.
+  EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
+                                       std::u16string(u"p4ssw0rd")));
+  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  controller_no_auth->OnCredentialSelected(credentials[0]);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.FilledCredentialWasFromAndroidApp", false, 1);
+
+  auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
+  ASSERT_EQ(1u, entries.size());
+  test_recorder().ExpectEntryMetric(
+      entries[0], UkmBuilder::kUserActionName,
+      static_cast<int64_t>(
+          TouchToFillController::UserAction::kSelectedCredential));
+}
+
+TEST_F(TouchToFillControllerTest, Show_And_Fill_No_Auth_Available) {
   UiCredential credentials[] = {
       MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
 
@@ -122,6 +176,10 @@
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
                                        std::u16string(u"p4ssw0rd")));
   EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+
+  EXPECT_CALL(*authenticator(), CanAuthenticate())
+      .WillOnce(Return(password_manager::BiometricsAvailability::kNoHardware));
+
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
   histogram_tester().ExpectUniqueSample(
       "PasswordManager.TouchToFill.NumCredentialsShown", 1, 1);
@@ -136,6 +194,43 @@
           TouchToFillController::UserAction::kSelectedCredential));
 }
 
+TEST_F(TouchToFillControllerTest, Show_And_Fill_Auth_Available_Success) {
+  UiCredential credentials[] = {
+      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
+
+  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
+                           ElementsAreArray(credentials)));
+  touch_to_fill_controller().Show(credentials, driver().AsWeakPtr());
+
+  EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"alice"),
+                                       std::u16string(u"p4ssw0rd")));
+  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+
+  EXPECT_CALL(*authenticator(), CanAuthenticate())
+      .WillOnce(Return(password_manager::BiometricsAvailability::kAvailable));
+  EXPECT_CALL(*authenticator(), Authenticate(credentials[0], _))
+      .WillOnce(RunOnceCallback<1>(true));
+  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
+}
+
+TEST_F(TouchToFillControllerTest, Show_And_Fill_Auth_Available_Failure) {
+  UiCredential credentials[] = {
+      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
+
+  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
+                           ElementsAreArray(credentials)));
+  touch_to_fill_controller().Show(credentials, driver().AsWeakPtr());
+
+  EXPECT_CALL(driver(), FillSuggestion(_, _)).Times(0);
+  EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(true)));
+
+  EXPECT_CALL(*authenticator(), CanAuthenticate())
+      .WillOnce(Return(password_manager::BiometricsAvailability::kAvailable));
+  EXPECT_CALL(*authenticator(), Authenticate(credentials[0], _))
+      .WillOnce(RunOnceCallback<1>(false));
+  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
+}
+
 TEST_F(TouchToFillControllerTest, Show_Empty) {
   EXPECT_CALL(view(), Show).Times(0);
   touch_to_fill_controller().Show({}, driver().AsWeakPtr());
@@ -181,6 +276,8 @@
   EXPECT_CALL(driver(), FillSuggestion(std::u16string(u"bob"),
                                        std::u16string(u"s3cr3t")));
   EXPECT_CALL(driver(), TouchToFillClosed(ShowVirtualKeyboard(false)));
+  EXPECT_CALL(*authenticator(), CanAuthenticate())
+      .WillOnce(Return(password_manager::BiometricsAvailability::kNoHardware));
   touch_to_fill_controller().OnCredentialSelected(credentials[1]);
   histogram_tester().ExpectUniqueSample(
       "PasswordManager.TouchToFill.NumCredentialsShown", 2, 1);
@@ -244,3 +341,26 @@
       entries[0], UkmBuilder::kUserActionName,
       static_cast<int64_t>(TouchToFillController::UserAction::kDismissed));
 }
+
+TEST_F(TouchToFillControllerTest, DestroyedWhileAuthRunning) {
+  std::unique_ptr<TouchToFillController> controller = CreateAuthController();
+  std::unique_ptr<MockTouchToFillView> mock_view =
+      std::make_unique<MockTouchToFillView>();
+  MockTouchToFillView* weak_view = mock_view.get();
+  controller->set_view(std::move(mock_view));
+
+  UiCredential credentials[] = {
+      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
+
+  EXPECT_CALL(*weak_view, Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
+                               ElementsAreArray(credentials)));
+  controller->Show(credentials, driver().AsWeakPtr());
+
+  EXPECT_CALL(*authenticator(), CanAuthenticate)
+      .WillOnce(Return(password_manager::BiometricsAvailability::kAvailable));
+  EXPECT_CALL(*authenticator(), Authenticate(credentials[0], _));
+  controller->OnCredentialSelected(credentials[0]);
+
+  EXPECT_CALL(*authenticator(), Cancel);
+  controller.reset();
+}
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.cc b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
index d39d26bc..62a7849 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
@@ -47,6 +47,8 @@
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
+#include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/language/core/browser/pref_names.h"
@@ -288,7 +290,7 @@
                          arc::mojom::WindowInfoPtr window_info) {
   DCHECK(!launch_intent.has_value() || !launch_intent->empty());
   if (user_action != UserInteractionType::NOT_USER_INITIATED)
-    UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction", user_action);
+    arc::ArcMetricsService::RecordArcUserInteraction(context, user_action);
 
   Profile* const profile = Profile::FromBrowserContext(context);
 
diff --git a/chrome/browser/ui/app_list/chrome_app_list_item.cc b/chrome/browser/ui/app_list/chrome_app_list_item.cc
index 74f1408..827922d 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_item.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_item.cc
@@ -169,15 +169,14 @@
 void ChromeAppListItem::SetIcon(const gfx::ImageSkia& icon) {
   metadata_->icon = icon;
   metadata_->icon.EnsureRepsForSupportedScales();
+  metadata_->badge_color =
+      ash::NotificationBadgeColorCache::GetInstance().GetBadgeColorForApp(id(),
+                                                                          icon);
+
   AppListModelUpdater* updater = model_updater();
   if (updater) {
     updater->SetItemIcon(id(), metadata_->icon);
-
-    // Calculate and set the notification badge color.
-    SkColor current_badge_color =
-        ash::NotificationBadgeColorCache::GetInstance().GetBadgeColorForApp(
-            id(), icon);
-    updater->SetNotificationBadgeColor(id(), current_badge_color);
+    updater->SetNotificationBadgeColor(id(), metadata_->badge_color);
   }
 }
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
index 55b68cd5..c7f99efa 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
@@ -22,11 +22,9 @@
 // HoldingSpaceDownloadsDelegate -----------------------------------------------
 
 HoldingSpaceDownloadsDelegate::HoldingSpaceDownloadsDelegate(
-    Profile* profile,
-    HoldingSpaceModel* model,
-    ItemDownloadedCallback item_downloaded_callback)
-    : HoldingSpaceKeyedServiceDelegate(profile, model),
-      item_downloaded_callback_(item_downloaded_callback) {}
+    HoldingSpaceKeyedService* service,
+    HoldingSpaceModel* model)
+    : HoldingSpaceKeyedServiceDelegate(service, model) {}
 
 HoldingSpaceDownloadsDelegate::~HoldingSpaceDownloadsDelegate() = default;
 
@@ -121,7 +119,8 @@
 
 void HoldingSpaceDownloadsDelegate::ManagerGoingDown(
     content::DownloadManager* manager) {
-  RemoveObservers();
+  download_manager_observation_.Reset();
+  download_item_observations_.RemoveAllObservations();
 }
 
 void HoldingSpaceDownloadsDelegate::OnDownloadCreated(
@@ -150,17 +149,13 @@
   }
 }
 
+// TODO(crbug.com/1184438): Support in-progress downloads.
 void HoldingSpaceDownloadsDelegate::OnDownloadCompleted(
     HoldingSpaceItem::Type type,
     const base::FilePath& file_path) {
   DCHECK(HoldingSpaceItem::IsDownload(type));
   if (!is_restoring_persistence())
-    item_downloaded_callback_.Run(type, file_path);
-}
-
-void HoldingSpaceDownloadsDelegate::RemoveObservers() {
-  download_manager_observation_.Reset();
-  download_item_observations_.RemoveAllObservations();
+    service()->AddDownload(type, file_path, /*progress=*/1.f);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
index 3c30d28a..adfc7b3 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
@@ -21,23 +21,14 @@
 namespace ash {
 
 // A delegate of `HoldingSpaceKeyedService` tasked with monitoring the status of
-// of downloads and notifying a callback on download completion.
+// of downloads on its behalf.
 class HoldingSpaceDownloadsDelegate : public HoldingSpaceKeyedServiceDelegate,
                                       public arc::ArcIntentHelperObserver,
                                       public content::DownloadManager::Observer,
                                       public download::DownloadItem::Observer {
  public:
-  // Callback to be invoked when a download is completed. Note that the
-  // specified type must be a download type. Also note that this callback will
-  // only be invoked after holding space persistence is restored.
-  using ItemDownloadedCallback =
-      base::RepeatingCallback<void(HoldingSpaceItem::Type,
-                                   const base::FilePath&)>;
-
-  HoldingSpaceDownloadsDelegate(
-      Profile* profile,
-      HoldingSpaceModel* model,
-      ItemDownloadedCallback item_downloaded_callback);
+  HoldingSpaceDownloadsDelegate(HoldingSpaceKeyedService* service,
+                                HoldingSpaceModel* model);
   HoldingSpaceDownloadsDelegate(const HoldingSpaceDownloadsDelegate&) = delete;
   HoldingSpaceDownloadsDelegate& operator=(
       const HoldingSpaceDownloadsDelegate&) = delete;
@@ -72,12 +63,6 @@
   void OnDownloadCompleted(HoldingSpaceItem::Type type,
                            const base::FilePath& file_path);
 
-  // Removes all observers.
-  void RemoveObservers();
-
-  // Callback to invoke when a download is completed.
-  ItemDownloadedCallback item_downloaded_callback_;
-
   base::ScopedObservation<arc::ArcIntentHelperBridge,
                           arc::ArcIntentHelperObserver>
       arc_intent_helper_observation_{this};
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
index 56722815..bb4bcd76 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
@@ -119,9 +119,9 @@
 // HoldingSpaceFileSystemDelegate ----------------------------------------------
 
 HoldingSpaceFileSystemDelegate::HoldingSpaceFileSystemDelegate(
-    Profile* profile,
+    HoldingSpaceKeyedService* service,
     HoldingSpaceModel* model)
-    : HoldingSpaceKeyedServiceDelegate(profile, model),
+    : HoldingSpaceKeyedServiceDelegate(service, model),
       file_system_watcher_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h
index e828bfe3..47e1a81 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h
@@ -41,7 +41,8 @@
       public drivefs::DriveFsHostObserver,
       public file_manager::VolumeManagerObserver {
  public:
-  HoldingSpaceFileSystemDelegate(Profile* profile, HoldingSpaceModel* model);
+  HoldingSpaceFileSystemDelegate(HoldingSpaceKeyedService* service,
+                                 HoldingSpaceModel* model);
   HoldingSpaceFileSystemDelegate(const HoldingSpaceFileSystemDelegate&) =
       delete;
   HoldingSpaceFileSystemDelegate& operator=(
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
index f34e36a..83b64b7d 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.h"
+#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_util.h"
 #include "components/account_id/account_id.h"
@@ -216,9 +217,10 @@
 
 void HoldingSpaceKeyedService::AddDownload(
     HoldingSpaceItem::Type type,
-    const base::FilePath& download_file) {
+    const base::FilePath& download_file,
+    const base::Optional<float>& progress) {
   DCHECK(HoldingSpaceItem::IsDownload(type));
-  AddItemOfType(type, download_file);
+  AddItemOfType(type, download_file, progress);
 }
 
 void HoldingSpaceKeyedService::AddNearbyShare(
@@ -267,15 +269,17 @@
   holding_space_model_.AddItems(std::move(items));
 }
 
-void HoldingSpaceKeyedService::AddItemOfType(HoldingSpaceItem::Type type,
-                                             const base::FilePath& file_path) {
+void HoldingSpaceKeyedService::AddItemOfType(
+    HoldingSpaceItem::Type type,
+    const base::FilePath& file_path,
+    const base::Optional<float>& progress) {
   const GURL file_system_url =
       holding_space_util::ResolveFileSystemUrl(profile_, file_path);
   if (file_system_url.is_empty())
     return;
 
   AddItem(HoldingSpaceItem::CreateFileBackedItem(
-      type, file_path, file_system_url,
+      type, file_path, file_system_url, progress,
       base::BindOnce(&holding_space_util::ResolveImage, &thumbnail_loader_)));
 }
 
@@ -331,21 +335,15 @@
 
   // The `HoldingSpaceDownloadsDelegate` monitors the status of downloads.
   delegates_.push_back(std::make_unique<HoldingSpaceDownloadsDelegate>(
-      profile_, &holding_space_model_,
-      /*item_downloaded_callback=*/
-      base::BindRepeating(&HoldingSpaceKeyedService::AddDownload,
-                          weak_factory_.GetWeakPtr())));
+      this, &holding_space_model_));
 
   // The `HoldingSpaceFileSystemDelegate` monitors the file system for changes.
   delegates_.push_back(std::make_unique<HoldingSpaceFileSystemDelegate>(
-      profile_, &holding_space_model_));
+      this, &holding_space_model_));
 
   // The `HoldingSpacePersistenceDelegate` manages holding space persistence.
   delegates_.push_back(std::make_unique<HoldingSpacePersistenceDelegate>(
-      profile_, &holding_space_model_, &thumbnail_loader_,
-      /*item_restored_callback=*/
-      base::BindRepeating(&HoldingSpaceKeyedService::AddItem,
-                          weak_factory_.GetWeakPtr()),
+      this, &holding_space_model_, &thumbnail_loader_,
       /*persistence_restored_callback=*/
       base::BindOnce(&HoldingSpaceKeyedService::OnPersistenceRestored,
                      weak_factory_.GetWeakPtr())));
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
index 9f93985..de042d47 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
@@ -14,7 +14,6 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_manager_observer.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_client_impl.h"
-#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h"
 #include "chrome/browser/ui/ash/thumbnail_loader.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "components/account_id/account_id.h"
@@ -37,6 +36,8 @@
 
 namespace ash {
 
+class HoldingSpaceKeyedServiceDelegate;
+
 // Browser context keyed service that:
 // *   Manages the temporary holding space per-profile data model.
 // *   Serves as an entry point to add holding space items from Chrome.
@@ -72,9 +73,12 @@
   std::vector<GURL> GetPinnedFiles() const;
 
   // Adds a download item of the specified `type` backed by the provided
-  // absolute file path. Note that `type` must refer to a download type.
+  // absolute file path.
+  // NOTE: `type` must refer to a download type.
+  // NOTE: If present, `progress` must be >= `0.f` and <= `1.f`.
   void AddDownload(HoldingSpaceItem::Type type,
-                   const base::FilePath& download_path);
+                   const base::FilePath& download_path,
+                   const base::Optional<float>& progress = 1.f);
 
   // Adds a nearby share item backed by the provided absolute file path.
   void AddNearbyShare(const base::FilePath& nearby_share_path);
@@ -96,8 +100,13 @@
 
   // Adds an item of the specified `type` backed by the provided absolute
   // `file_path` to the holding space model.
+  // NOTE: If present, `progress` must be >= `0.f` and <= `1.f`.
   void AddItemOfType(HoldingSpaceItem::Type type,
-                     const base::FilePath& file_path);
+                     const base::FilePath& file_path,
+                     const base::Optional<float>& progress = base::nullopt);
+
+  // Returns the `profile_` associated with this service.
+  Profile* profile() { return profile_; }
 
   const HoldingSpaceClient* client_for_testing() const {
     return &holding_space_client_;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.cc
index 627dc21..9799eb3 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.cc
@@ -26,11 +26,11 @@
 }
 
 HoldingSpaceKeyedServiceDelegate::HoldingSpaceKeyedServiceDelegate(
-    Profile* profile,
+    HoldingSpaceKeyedService* service,
     HoldingSpaceModel* model)
-    : profile_(profile), model_(model) {
-  // It is expected that `profile` already be ready prior to delegate creation.
-  DCHECK(GetProfileManager()->IsValidProfile(profile));
+    : service_(service), model_(model) {
+  // It's expected that `profile()` already be ready prior to delegate creation.
+  DCHECK(GetProfileManager()->IsValidProfile(profile()));
   holding_space_model_observation_.Observe(model);
 }
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h
index 9fce7457..f82e089 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_delegate.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/holding_space/holding_space_model.h"
 #include "ash/public/cpp/holding_space/holding_space_model_observer.h"
 #include "base/scoped_observation.h"
+#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
 
 class Profile;
 
@@ -32,12 +33,16 @@
   void NotifyPersistenceRestored();
 
  protected:
-  HoldingSpaceKeyedServiceDelegate(Profile* profile, HoldingSpaceModel* model);
+  HoldingSpaceKeyedServiceDelegate(HoldingSpaceKeyedService* service,
+                                   HoldingSpaceModel* model);
 
-  // Returns the `profile_` associated with the `HoldingSpaceKeyedService`.
-  Profile* profile() { return profile_; }
+  // Returns the `profile_` associated with the `service_`.
+  Profile* profile() { return service_->profile(); }
 
-  // Returns the holding space model owned by `HoldingSpaceKeyedService`.
+  // Returns the `service` which owns this delegate.
+  HoldingSpaceKeyedService* service() { return service_; }
+
+  // Returns the holding space model owned by `service_`.
   HoldingSpaceModel* model() { return model_; }
 
   // Returns if persistence is being restored.
@@ -54,7 +59,7 @@
   // Invoked when holding space persistence has been restored.
   virtual void OnPersistenceRestored();
 
-  Profile* const profile_;
+  HoldingSpaceKeyedService* const service_;
   HoldingSpaceModel* const model_;
 
   // If persistence is being restored.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.cc
index 310f0a9e..5f78dcf 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.cc
@@ -33,14 +33,12 @@
 constexpr char HoldingSpacePersistenceDelegate::kPersistencePath[];
 
 HoldingSpacePersistenceDelegate::HoldingSpacePersistenceDelegate(
-    Profile* profile,
+    HoldingSpaceKeyedService* service,
     HoldingSpaceModel* model,
     ThumbnailLoader* thumbnail_loader,
-    ItemRestoredCallback item_restored_callback,
     PersistenceRestoredCallback persistence_restored_callback)
-    : HoldingSpaceKeyedServiceDelegate(profile, model),
+    : HoldingSpaceKeyedServiceDelegate(service, model),
       thumbnail_loader_(thumbnail_loader),
-      item_restored_callback_(item_restored_callback),
       persistence_restored_callback_(std::move(persistence_restored_callback)) {
 }
 
@@ -59,6 +57,7 @@
   RestoreModelFromPersistence();
 }
 
+// TODO(crbug.com/1184438): Do not persist in-progress `items`.
 void HoldingSpacePersistenceDelegate::OnHoldingSpaceItemsAdded(
     const std::vector<const HoldingSpaceItem*>& items) {
   if (is_restoring_persistence())
@@ -87,6 +86,7 @@
   });
 }
 
+// TODO(crbug.com/1184438): Persist `items` no longer in-progress.
 void HoldingSpacePersistenceDelegate::OnHoldingSpaceItemUpdated(
     const HoldingSpaceItem* item) {
   if (is_restoring_persistence())
@@ -125,10 +125,11 @@
             base::Value::AsDictionaryValue(persisted_holding_space_item),
             base::BindOnce(&holding_space_util::ResolveImage,
                            base::Unretained(thumbnail_loader_)));
-    if (ShouldIgnoreItem(profile(), holding_space_item.get()))
-      continue;
-    item_restored_callback_.Run(std::move(holding_space_item));
+
+    if (!ShouldIgnoreItem(profile(), holding_space_item.get()))
+      service()->AddItem(std::move(holding_space_item));
   }
+
   // Notify completion of persistence restoration.
   std::move(persistence_restored_callback_).Run();
 }
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h
index 701bf23..561c3046 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h
@@ -34,19 +34,13 @@
   // NOTE: Any changes to persistence must be backwards compatible.
   static constexpr char kPersistencePath[] = "ash.holding_space.items";
 
-  // Callback to invoke when the specified holding space item has been restored
-  // from persistence.
-  using ItemRestoredCallback =
-      base::RepeatingCallback<void(HoldingSpaceItemPtr)>;
-
   // Callback to invoke when holding space persistence has been restored.
   using PersistenceRestoredCallback = base::OnceClosure;
 
   HoldingSpacePersistenceDelegate(
-      Profile* profile,
+      HoldingSpaceKeyedService* service,
       HoldingSpaceModel* model,
       ThumbnailLoader* thumbnail_loader,
-      ItemRestoredCallback item_restored_callback,
       PersistenceRestoredCallback persistence_restored_callback);
   HoldingSpacePersistenceDelegate(const HoldingSpacePersistenceDelegate&) =
       delete;
@@ -72,9 +66,6 @@
   // Owned by `HoldingSpaceKeyedService`.
   ThumbnailLoader* const thumbnail_loader_;
 
-  // Callback to invoke when an item has been restored from persistence.
-  ItemRestoredCallback item_restored_callback_;
-
   // Callback to invoke when holding space persistence has been restored.
   PersistenceRestoredCallback persistence_restored_callback_;
 
diff --git a/chrome/browser/ui/autofill/payments/local_card_migration_bubble_controller_impl.cc b/chrome/browser/ui/autofill/payments/local_card_migration_bubble_controller_impl.cc
index ad05426..2d624bc 100644
--- a/chrome/browser/ui/autofill/payments/local_card_migration_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/payments/local_card_migration_bubble_controller_impl.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/payments/local_card_migration_strike_database.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
diff --git a/chrome/browser/ui/autofill/payments/local_card_migration_dialog_controller_impl.cc b/chrome/browser/ui/autofill/payments/local_card_migration_dialog_controller_impl.cc
index 289aea4..8a432c74 100644
--- a/chrome/browser/ui/autofill/payments/local_card_migration_dialog_controller_impl.cc
+++ b/chrome/browser/ui/autofill/payments/local_card_migration_dialog_controller_impl.cc
@@ -29,7 +29,7 @@
 #include "components/autofill/core/browser/payments/local_card_migration_manager.h"
 #include "components/autofill/core/browser/payments/local_card_migration_strike_database.h"
 #include "components/autofill/core/browser/payments/payments_service_url.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/validation.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_features.h"
diff --git a/chrome/browser/ui/views/create_application_shortcut_view.cc b/chrome/browser/ui/views/create_application_shortcut_view.cc
index bf0ea0e..191814d9 100644
--- a/chrome/browser/ui/views/create_application_shortcut_view.cc
+++ b/chrome/browser/ui/views/create_application_shortcut_view.cc
@@ -62,7 +62,6 @@
     const extensions::Extension* app,
     base::OnceCallback<void(bool)> close_callback)
     : CreateChromeApplicationShortcutView(profile, std::move(close_callback)) {
-  SetModalType(ui::MODAL_TYPE_WINDOW);
   // Get shortcut and icon information; needed for creating the shortcut.
   web_app::GetShortcutInfoForApp(
       app, profile,
@@ -86,6 +85,7 @@
     Profile* profile,
     base::OnceCallback<void(bool)> close_callback)
     : profile_(profile), close_callback_(std::move(close_callback)) {
+  SetModalType(ui::MODAL_TYPE_WINDOW);
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_COMMIT));
   set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
diff --git a/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc b/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
index 5e0735a..a211a2a 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.h"
 
+#include "base/bind.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/omnibox/omnibox_theme.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -80,14 +81,6 @@
 
   OmniboxPopupModel::Selection selection() { return selection_; }
 
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override {
-    // MdTextButton uses custom colors when creating ink drop highlight.
-    // We need the base implementation that uses GetInkDropBaseColor for
-    // highlight.
-    return views::InkDropHostView::CreateInkDropHighlight();
-  }
-
   void OnThemeChanged() override {
     MdTextButton::OnThemeChanged();
     // We can't use colors from NativeTheme as the omnibox theme might be
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 6edffaa0..3733ec0 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -115,34 +115,6 @@
          command_id <= AppMenuModel::kMaxRecentTabsCommandId;
 }
 
-// Subclass of ImageButton whose preferred size includes the size of the border.
-class FullscreenButton : public ImageButton {
- public:
-  METADATA_HEADER(FullscreenButton);
-  explicit FullscreenButton(PressedCallback callback)
-      : ImageButton(std::move(callback)) {}
-  FullscreenButton(const FullscreenButton&) = delete;
-  FullscreenButton& operator=(const FullscreenButton&) = delete;
-
-  // Overridden from ImageButton.
-  gfx::Size CalculatePreferredSize() const override {
-    gfx::Size pref = ImageButton::CalculatePreferredSize();
-    if (border()) {
-      gfx::Insets insets = border()->GetInsets();
-      pref.Enlarge(insets.width(), insets.height());
-    }
-    return pref;
-  }
-
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
-    ImageButton::GetAccessibleNodeData(node_data);
-    node_data->role = ax::mojom::Role::kMenuItem;
-  }
-};
-
-BEGIN_METADATA(FullscreenButton, ImageButton)
-END_METADATA
-
 // Combination border/background for the buttons contained in the menu. The
 // painting of the border/background is done here as LabelButton does not always
 // paint the border.
@@ -311,27 +283,34 @@
       int string_id,
       InMenuButtonBackground::ButtonType type,
       int index) {
-    return CreateButtonWithAccName(std::move(callback), string_id, type, index,
-                                   string_id,
-                                   /*add_accelerator_text*/ true);
+    return CreateButtonWithAccessibleName(
+        std::move(callback), string_id, type, index, string_id,
+        /*add_accelerator_text=*/true,
+        /*use_accessible_name_as_tooltip_text=*/false);
   }
 
-  InMenuButton* CreateButtonWithAccName(views::Button::PressedCallback callback,
-                                        int string_id,
-                                        InMenuButtonBackground::ButtonType type,
-                                        int index,
-                                        int acc_string_id,
-                                        bool add_accelerator_text) {
+  InMenuButton* CreateButtonWithAccessibleName(
+      views::Button::PressedCallback callback,
+      int string_id,
+      InMenuButtonBackground::ButtonType type,
+      int index,
+      int accessible_name_id,
+      bool add_accelerator_text,
+      bool use_accessible_name_as_tooltip_text) {
     // Should only be invoked during construction when |menu_| is valid.
     DCHECK(menu_);
+
     InMenuButton* button = new InMenuButton(
         std::move(callback),
         gfx::RemoveAccelerator(l10n_util::GetStringUTF16(string_id)));
     button->Init(type);
     button->SetAccessibleName(GetAccessibleNameForAppMenuItem(
-        menu_model_, index, acc_string_id, add_accelerator_text));
+        menu_model_, index, accessible_name_id, add_accelerator_text));
     button->set_tag(index);
     button->SetEnabled(menu_model_->IsEnabledAt(index));
+    if (use_accessible_name_as_tooltip_text) {
+      button->SetTooltipText(l10n_util::GetStringUTF16(accessible_name_id));
+    }
 
     AddChildView(button);
     // all buttons on menu should must be a custom button in order for
@@ -355,6 +334,59 @@
 BEGIN_METADATA(AppMenuView, views::View)
 END_METADATA
 
+// Subclass of ImageButton whose preferred size includes the size of the border.
+class FullscreenButton : public ImageButton {
+ public:
+  METADATA_HEADER(FullscreenButton);
+  explicit FullscreenButton(PressedCallback callback,
+                            ButtonMenuItemModel* menu_model,
+                            int fullscreen_index)
+      : ImageButton(std::move(callback)) {
+    // Since |fullscreen_button_| will reside in a menu, make it ALWAYS
+    // focusable regardless of the platform.
+    SetFocusBehavior(FocusBehavior::ALWAYS);
+    set_tag(fullscreen_index);
+    SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER);
+    SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE);
+    SetBackground(std::make_unique<InMenuButtonBackground>(
+        InMenuButtonBackground::LEADING_BORDER));
+    SetTooltipText(l10n_util::GetStringUTF16(IDS_ACCNAME_FULLSCREEN));
+    SetAccessibleName(GetAccessibleNameForAppMenuItem(
+        menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN,
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+        // ChromeOS uses a dedicated "fullscreen" media key for fullscreen
+        // mode on most ChromeOS devices which cannot be specified in the
+        // standard way here, so omit the accelerator to avoid providing
+        // misleading or confusing information to screen reader users.
+        // See crbug.com/1110468 for more context.
+        /*add_accelerator_text=*/false
+#else
+        /*add_accelerator_text=*/true
+#endif
+        ));
+  }
+  FullscreenButton(const FullscreenButton&) = delete;
+  FullscreenButton& operator=(const FullscreenButton&) = delete;
+
+  // Overridden from ImageButton.
+  gfx::Size CalculatePreferredSize() const override {
+    gfx::Size pref = ImageButton::CalculatePreferredSize();
+    if (border()) {
+      gfx::Insets insets = border()->GetInsets();
+      pref.Enlarge(insets.width(), insets.height());
+    }
+    return pref;
+  }
+
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
+    ImageButton::GetAccessibleNodeData(node_data);
+    node_data->role = ax::mojom::Role::kMenuItem;
+  }
+};
+
+BEGIN_METADATA(FullscreenButton, ImageButton)
+END_METADATA
+
 }  // namespace
 
 // CutCopyPasteView ------------------------------------------------------------
@@ -449,11 +481,12 @@
     const auto activate = [](ButtonMenuItemModel* menu_model, int index) {
       menu_model->ActivatedAt(index);
     };
-    decrement_button_ = CreateButtonWithAccName(
+    decrement_button_ = CreateButtonWithAccessibleName(
         base::BindRepeating(activate, menu_model, decrement_index),
         IDS_ZOOM_MINUS2, InMenuButtonBackground::LEADING_BORDER,
         decrement_index, IDS_ACCNAME_ZOOM_MINUS2,
-        /*add_accelerator_text*/ false);
+        /*add_accelerator_text=*/false,
+        /*use_accessible_name_as_tooltip_text=*/true);
 
     zoom_label_ = new Label(base::FormatPercent(100));
     zoom_label_->SetAutoColorReadabilityEnabled(false);
@@ -472,41 +505,23 @@
 
     AddChildView(zoom_label_);
 
-    increment_button_ = CreateButtonWithAccName(
+    increment_button_ = CreateButtonWithAccessibleName(
         base::BindRepeating(activate, menu_model, increment_index),
         IDS_ZOOM_PLUS2, InMenuButtonBackground::NO_BORDER, increment_index,
-        IDS_ACCNAME_ZOOM_PLUS2, /*add_accelerator_text*/ false);
+        IDS_ACCNAME_ZOOM_PLUS2, /*add_accelerator_text=*/false,
+        /*use_accessible_name_as_tooltip_text=*/true);
 
-    fullscreen_button_ = new FullscreenButton(base::BindRepeating(
-        [](AppMenu* menu, ButtonMenuItemModel* menu_model, int index) {
-          menu->CancelAndEvaluate(menu_model, index);
-        },
-        menu, menu_model, fullscreen_index));
+    fullscreen_button_ = new FullscreenButton(
+        base::BindRepeating(
+            [](AppMenu* menu, ButtonMenuItemModel* menu_model, int index) {
+              menu->CancelAndEvaluate(menu_model, index);
+            },
+            menu, menu_model, fullscreen_index),
+        menu_model, fullscreen_index);
+
     // all buttons on menu should must be a custom button in order for
     // the keyboard navigation to work.
     DCHECK(Button::AsButton(fullscreen_button_));
-
-    // Since |fullscreen_button_| will reside in a menu, make it ALWAYS
-    // focusable regardless of the platform.
-    fullscreen_button_->SetFocusBehavior(FocusBehavior::ALWAYS);
-    fullscreen_button_->set_tag(fullscreen_index);
-    fullscreen_button_->SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER);
-    fullscreen_button_->SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE);
-    fullscreen_button_->SetBackground(std::make_unique<InMenuButtonBackground>(
-        InMenuButtonBackground::LEADING_BORDER));
-    fullscreen_button_->SetAccessibleName(GetAccessibleNameForAppMenuItem(
-        menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN,
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-        // ChromeOS uses a dedicated "fullscreen" media key for fullscreen
-        // mode on most ChromeOS devices which cannot be specified in the
-        // standard way here, so omit the accelerator to avoid providing
-        // misleading or confusing information to screen reader users.
-        // See crbug.com/1110468 for more context.
-        /*add_accelerator_text*/ false
-#else
-        /*add_accelerator_text*/ true
-#endif
-        ));
     AddChildView(fullscreen_button_);
 
     // The max width for `zoom_label_` should not be valid until the calls into
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index 96036144..c2c7462 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -176,6 +176,26 @@
         this));
   }
 
+  SetCreateInkDropMaskCallback(base::BindRepeating(
+      [](ToolbarButton* host) -> std::unique_ptr<views::InkDropMask> {
+        if (host->has_in_product_help_promo_) {
+          // This gets the latest ink drop insets. |SetTrailingMargin()| is
+          // called whenever our margins change (i.e. due to the window
+          // maximizing or minimizing) and updates our internal padding property
+          // accordingly.
+          const gfx::Insets ink_drop_insets = GetToolbarInkDropInsets(host);
+          const float corner_radius = (host->height() - ink_drop_insets.top() -
+                                       ink_drop_insets.bottom()) /
+                                      2.0f;
+          return std::make_unique<PulsingInkDropMask>(
+              host->ink_drop_container(), host->size(), ink_drop_insets,
+              corner_radius, kFeaturePromoPulseInsetDip);
+        }
+        return std::make_unique<views::PathInkDropMask>(host->size(),
+                                                        GetHighlightPath(host));
+      },
+      this));
+
   // Make sure icons are flipped by default so that back, forward, etc. follows
   // UI direction.
   SetFlipCanvasOnPaintForRTLUI(true);
@@ -563,22 +583,6 @@
                                     : views::LabelButton::GetTooltipText(p);
 }
 
-std::unique_ptr<views::InkDropMask> ToolbarButton::CreateInkDropMask() const {
-  if (has_in_product_help_promo_) {
-    // This gets the latest ink drop insets. |SetTrailingMargin()| is called
-    // whenever our margins change (i.e. due to the window maximizing or
-    // minimizing) and updates our internal padding property accordingly.
-    const gfx::Insets ink_drop_insets = GetToolbarInkDropInsets(this);
-    const float corner_radius =
-        (height() - ink_drop_insets.top() - ink_drop_insets.bottom()) / 2.0f;
-    return std::make_unique<PulsingInkDropMask>(ink_drop_container(), size(),
-                                                ink_drop_insets, corner_radius,
-                                                kFeaturePromoPulseInsetDip);
-  }
-
-  return views::LabelButton::CreateInkDropMask();
-}
-
 SkColor ToolbarButton::GetInkDropBaseColor() const {
   // Ensure this doesn't get called when InstallableInkDrops are enabled.
   DCHECK(!base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
@@ -618,9 +622,9 @@
 
   has_in_product_help_promo_ = has_in_product_help_promo;
 
-  // We override GetInkDropBaseColor() and CreateInkDropMask(), returning the
-  // promo values if we are showing an in-product help promo. Calling
-  // HostSizeChanged() will force the new mask and color to be fetched.
+  // We override GetInkDropBaseColor() and call SetCreateInkDropMaskCallback(),
+  // returning the promo values if we are showing an in-product help promo.
+  // Calling HostSizeChanged() will force the new mask and color to be fetched.
   //
   // TODO(collinbaker): Consider adding explicit way to recreate mask instead
   // of relying on HostSizeChanged() to do so.
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 383d32d..897f6bb 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -116,7 +116,6 @@
   void OnGestureEvent(ui::GestureEvent* event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   std::u16string GetTooltipText(const gfx::Point& p) const override;
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
   views::InkDrop* GetInkDrop() override;
   SkColor GetInkDropBaseColor() const override;
 
diff --git a/chrome/browser/ui/webui/discards/discards.mojom b/chrome/browser/ui/webui/discards/discards.mojom
index 36c3b52..a328f04 100644
--- a/chrome/browser/ui/webui/discards/discards.mojom
+++ b/chrome/browser/ui/webui/discards/discards.mojom
@@ -100,6 +100,9 @@
 
   url.mojom.Url main_frame_url;
 
+  // The id of the frame that opened this page via window.open, if any.
+  int64 opener_frame_id;
+
   // The id of the frame that embedded this page, if any.
   int64 embedder_frame_id;
 
diff --git a/chrome/browser/ui/webui/discards/graph_dump_impl.cc b/chrome/browser/ui/webui/discards/graph_dump_impl.cc
index f6cbd1f..33b3596 100644
--- a/chrome/browser/ui/webui/discards/graph_dump_impl.cc
+++ b/chrome/browser/ui/webui/discards/graph_dump_impl.cc
@@ -291,6 +291,13 @@
   RemoveNode(page_node);
 }
 
+void DiscardsGraphDumpImpl::OnOpenerFrameNodeChanged(
+    const performance_manager::PageNode* page_node,
+    const performance_manager::FrameNode* previous_opener) {
+  DCHECK(HasNode(page_node));
+  SendPageNotification(page_node, false);
+}
+
 void DiscardsGraphDumpImpl::OnEmbedderFrameNodeChanged(
     const performance_manager::PageNode* page_node,
     const performance_manager::FrameNode*,
@@ -505,6 +512,7 @@
 
   page_info->id = GetNodeId(page_node);
   page_info->main_frame_url = page_node->GetMainFrameUrl();
+  page_info->opener_frame_id = GetNodeId(page_node->GetOpenerFrameNode());
   page_info->embedder_frame_id = GetNodeId(page_node->GetEmbedderFrameNode());
   page_info->description_json = ToJSON(
       graph_->GetNodeDataDescriberRegistry()->DescribeNodeData(page_node));
diff --git a/chrome/browser/ui/webui/discards/graph_dump_impl.h b/chrome/browser/ui/webui/discards/graph_dump_impl.h
index 1a7e2fa..c743d59f 100644
--- a/chrome/browser/ui/webui/discards/graph_dump_impl.h
+++ b/chrome/browser/ui/webui/discards/graph_dump_impl.h
@@ -113,6 +113,9 @@
   void OnPageNodeAdded(const performance_manager::PageNode* page_node) override;
   void OnBeforePageNodeRemoved(
       const performance_manager::PageNode* page_node) override;
+  void OnOpenerFrameNodeChanged(
+      const performance_manager::PageNode* page_node,
+      const performance_manager::FrameNode* previous_opener) override;
   void OnEmbedderFrameNodeChanged(
       const performance_manager::PageNode* page_node,
       const performance_manager::FrameNode* previous_embedder,
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
index a1dd5328..604e645 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
@@ -11,6 +11,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
+#include "base/trace_event/trace_event.h"
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
@@ -196,6 +197,8 @@
 TabStripUIHandler::~TabStripUIHandler() = default;
 
 void TabStripUIHandler::NotifyLayoutChanged() {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:NotifyLayoutChanged");
   if (!IsJavascriptAllowed())
     return;
   FireWebUIListener("layout-changed", embedder_->GetLayout().AsDictionary());
@@ -221,6 +224,7 @@
 
 // TabStripModelObserver:
 void TabStripUIHandler::OnTabGroupChanged(const TabGroupChange& change) {
+  TRACE_EVENT0("browser", "custom_metric:TabStripUIHandler:OnTabGroupChanged");
   switch (change.type) {
     case TabGroupChange::kCreated:
     case TabGroupChange::kEditorOpened:
@@ -264,6 +268,8 @@
     base::Optional<tab_groups::TabGroupId> group,
     content::WebContents* contents,
     int index) {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:TabGroupedStateChanged");
   int tab_id = extensions::ExtensionTabUtil::GetTabId(contents);
   if (group.has_value()) {
     FireWebUIListener("tab-group-state-changed", base::Value(tab_id),
@@ -279,6 +285,8 @@
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
     const TabStripSelectionChange& selection) {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:OnTabStripModelChanged");
   if (tab_strip_model->empty())
     return;
 
@@ -361,6 +369,7 @@
 void TabStripUIHandler::TabChangedAt(content::WebContents* contents,
                                      int index,
                                      TabChangeType change_type) {
+  TRACE_EVENT0("browser", "custom_metric:TabStripUIHandler:TabChangedAt");
   FireWebUIListener("tab-updated", GetTabData(contents, index));
 }
 
@@ -594,6 +603,7 @@
 }
 
 void TabStripUIHandler::HandleGetTabs(const base::ListValue* args) {
+  TRACE_EVENT0("browser", "custom_metric:TabStripUIHandler:HandleGetTabs");
   AllowJavascript();
   const base::Value& callback_id = args->GetList()[0];
 
@@ -606,6 +616,8 @@
 }
 
 void TabStripUIHandler::HandleGetGroupVisualData(const base::ListValue* args) {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:HandleGetGroupVisualData");
   AllowJavascript();
   const base::Value& callback_id = args->GetList()[0];
 
@@ -622,6 +634,8 @@
 }
 
 void TabStripUIHandler::HandleGetThemeColors(const base::ListValue* args) {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:HandleGetThemeColors");
   AllowJavascript();
   const base::Value& callback_id = args->GetList()[0];
 
@@ -900,6 +914,7 @@
 }
 
 void TabStripUIHandler::HandleGetLayout(const base::ListValue* args) {
+  TRACE_EVENT0("browser", "custom_metric:TabStripUIHandler:HandleGetLayout");
   AllowJavascript();
   const base::Value& callback_id = args->GetList()[0];
 
@@ -908,6 +923,8 @@
 }
 
 void TabStripUIHandler::HandleSetThumbnailTracked(const base::ListValue* args) {
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:HandleSetThumbnailTracked");
   AllowJavascript();
 
   int tab_id = args->GetList()[0].GetInt();
@@ -957,6 +974,8 @@
     ThumbnailTracker::CompressedThumbnailData image) {
   // Send base-64 encoded image to JS side. If |image| is blank (i.e.
   // there is no data), send a blank URI.
+  TRACE_EVENT0("browser",
+               "custom_metric:TabStripUIHandler:HandleThumbnailUpdate");
   std::string data_uri;
   if (image)
     data_uri = webui::MakeDataURIForImage(base::make_span(image->data), "jpeg");
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 591975d..2a7be777 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1620193489-ecb74f092b3d0ff9732042da0ab458ae29c4bd49.profdata
+chrome-win32-master-1620226734-d8c61dfceebb448f59c384822e9494e9858c2e85.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 6ea73e6..ec12e657 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1620205001-59a405d5d3cc1e81f6386200200564e6437ec397.profdata
+chrome-win64-master-1620226734-f43a2a9eee8868818ee022f83a610a4f6a63a530.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 29b90e0..757a71a 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -772,6 +772,10 @@
 const base::FeatureParam<std::string> kPrivacySandboxSettingsURL{
     &kPrivacySandboxSettings, "website-url", "https://www.privacysandbox.com"};
 
+// Enables additional control set 2 on the privacy sandbox settings page.
+const base::Feature kPrivacySandboxSettings2{"PrivacySandboxSettings2",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables push subscriptions keeping Chrome running in the
 // background when closed.
 const base::Feature kPushMessagingBackgroundMode{
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 37c244c..6fe2e9e3 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -119,6 +119,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::FeatureParam<std::string> kPrivacySandboxSettingsURL;
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPrivacySandboxSettings2;
+
 #if defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kCpuAffinityRestrictToLittleCores;
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 64e47217..b0b6651 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -637,9 +637,6 @@
 
 // Sets the market URL for Chrome for use in testing.
 const char kMarketUrlForTesting[] = "market-url-for-testing";
-
-// Custom WebAPK server URL for the sake of testing.
-const char kWebApkServerUrl[] = "webapk-server-url";
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -811,6 +808,11 @@
 const char kGuest[] = "guest";
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_ANDROID)
+// Custom WebAPK server URL for the sake of testing.
+const char kWebApkServerUrl[] = "webapk-server-url";
+#endif
+
 #if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 // Uses the system default printer as the initially selected destination in
 // print preview, instead of the most recently used destination.
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 1aa769d..26c5fb6 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -195,7 +195,6 @@
 extern const char kForceShowUpdateMenuItemCustomSummary[];
 extern const char kForceUpdateMenuType[];
 extern const char kMarketUrlForTesting[];
-extern const char kWebApkServerUrl[];
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -259,6 +258,10 @@
 extern const char kGuest[];
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_ANDROID)
+extern const char kWebApkServerUrl[];
+#endif
+
 #if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
 extern const char kUseSystemDefaultPrinter[];
 #endif
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser.cc b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
index 186eda7..d6c583c6 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
@@ -7,6 +7,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/pattern.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
@@ -64,6 +65,43 @@
   return true;
 }
 
+// Converts the given robots rule pattern to a pattern compatible with
+// |base::MatchPattern|.
+//
+// Robots rule patterns have slightly different semantics than the pattern
+// inputs for |base::MatchPattern|. They support '*', which matches zero or more
+// of any character, and '$', which matches the end of the input string. On the
+// other hand, |base::MatchPattern| supports '*' and '?', zero or one of any
+// character, but not '$'.
+//
+// Both patterns are anchored at the beginning of the input string, but
+// |base::MatchPattern| is also implicitly anchored to the end of the string.
+// That is, the pattern must match the whole string in order to match.
+//
+// We can convert the given |robots_rule| to one that is compatible with
+// |base::MatchPattern| by taking care of optionally-present '$' character and
+// backslash-escaping any '?' characters, since they should be interpreted
+// literally .
+std::string ConvertRobotsRuleToGlob(const std::string& robots_rule) {
+  if (robots_rule.empty())
+    return "*";
+  std::string glob(robots_rule);
+  // Any '\' characters that appear in |robots_rule| are meant as literals. To
+  // prevent |base::MatchPattern| from interpreting bare '\' as an escape
+  // character, we replace each bare backslash with two backslashes.
+  base::ReplaceSubstringsAfterOffset(&glob, 0, "\\", "\\\\");
+  // |base::MatchPattern| treats '?' as a special symbol, but robots rule
+  // patterns do not. Escape each occurrence with a backslash.
+  base::ReplaceSubstringsAfterOffset(&glob, 0, "?", "\\?");
+  // |base::MatchPattern| implicitly anchors to the end of the string, but
+  // |robots rule patterns require an explicit trailing '$'.
+  if (glob.back() == '$')
+    glob.pop_back();
+  else
+    glob.push_back('*');
+  return glob;
+}
+
 void RecordRobotsRulesReceiveResultHistogram(
     RobotsRulesParser::SubresourceRedirectRobotsRulesReceiveResult result) {
   UMA_HISTOGRAM_ENUMERATION(
@@ -78,7 +116,10 @@
 }  // namespace
 
 bool RobotsRulesParser::RobotsRule::Match(const std::string& path) const {
-  return IsMatchingRobotsRule(path, pattern_);
+  const bool kCanonicalResult = IsMatchingRobotsRule(path, pattern_);
+  const bool kGlobResult = base::MatchPattern(path, glob_);
+  DCHECK_EQ(kCanonicalResult, kGlobResult);
+  return kGlobResult;
 }
 
 RobotsRulesParser::RobotsRulesParser(
@@ -113,12 +154,22 @@
   rules_receive_state_ = is_parse_success ? RulesReceiveState::kSuccess
                                           : RulesReceiveState::kParseFailed;
   if (is_parse_success) {
-    robots_rules_.reserve(robots_rules.image_ordered_rules_size());
+    robots_rules_.reserve(robots_rules.image_ordered_rules().size());
+    std::set<std::string> allowed_pattern_set;
+    std::set<std::string> disallowed_pattern_set;
     for (const auto& rule : robots_rules.image_ordered_rules()) {
       if (rule.has_allowed_pattern()) {
-        robots_rules_.emplace_back(true, rule.allowed_pattern());
+        const std::string& pattern = rule.allowed_pattern();
+        if (allowed_pattern_set.insert(pattern).second) {
+          robots_rules_.emplace_back(true, ConvertRobotsRuleToGlob(pattern),
+                                     pattern);
+        }
       } else if (rule.has_disallowed_pattern()) {
-        robots_rules_.emplace_back(false, rule.disallowed_pattern());
+        const std::string& pattern = rule.disallowed_pattern();
+        if (disallowed_pattern_set.insert(pattern).second) {
+          robots_rules_.emplace_back(false, ConvertRobotsRuleToGlob(pattern),
+                                     pattern);
+        }
       }
     }
     UMA_HISTOGRAM_COUNTS_1000("SubresourceRedirect.RobotRulesDecider.Count",
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser.h b/chrome/renderer/subresource_redirect/robots_rules_parser.h
index 00fd14c..91e94b9 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser.h
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser.h
@@ -94,12 +94,15 @@
 
   // Contains one robots.txt rule.
   struct RobotsRule {
-    RobotsRule(bool is_allow_rule, const std::string& pattern)
-        : is_allow_rule_(is_allow_rule), pattern_(pattern) {}
+    RobotsRule(bool is_allow_rule,
+               const std::string& glob,
+               const std::string& pattern)
+        : is_allow_rule_(is_allow_rule), glob_(glob), pattern_(pattern) {}
 
     bool Match(const std::string& path) const;
 
     const bool is_allow_rule_;
+    const std::string glob_;
     const std::string pattern_;
   };
 
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
index 227294d..afb5979 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
@@ -381,6 +381,62 @@
 }
 
 TEST_F(SubresourceRedirectRobotsRulesParserTest,
+       TestRulesHandleSpecialGlobSymbols) {
+  SetUpRobotsRules({
+      {kRuleTypeAllow, "/allo?ed"},
+      {kRuleTypeAllow, "/foo"},
+      {kRuleTypeAllow, "/bar$"},
+      {kRuleTypeAllow, "/boo$$"},
+      {kRuleTypeAllow, "/baz*"},
+      {kRuleTypeAllow, "/bop*$"},
+      {kRuleTypeAllow, "*startwithstar"},
+      {kRuleTypeAllow, "*anchoredtoend$"},
+      {kRuleTypeDisallow, "/"},
+  });
+  VerifyReceivedRobotsRulesCountHistogram(9);
+
+  // The '?' character is *not* treated as a special globbing symbol.
+  CheckRobotsRules("/allo?ed.jpg", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/allowed.jpg", RobotsRulesParser::CheckResult::kDisallowed);
+
+  // A pattern without a final '$' is not anchored to the end of the string.
+  CheckRobotsRules("/foo", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/foo.jpg", RobotsRulesParser::CheckResult::kAllowed);
+
+  // A pattern with a final '$' is anchored to the end of the string.
+  CheckRobotsRules("/bar", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/bar.jpg", RobotsRulesParser::CheckResult::kDisallowed);
+
+  // When a pattern ends with two '$' characters, the first '$' is a literal,
+  // and the second matches the end of the string.
+  CheckRobotsRules("/boo$", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/boo$$", RobotsRulesParser::CheckResult::kDisallowed);
+  CheckRobotsRules("/boo", RobotsRulesParser::CheckResult::kDisallowed);
+  CheckRobotsRules("/boo$.jpg", RobotsRulesParser::CheckResult::kDisallowed);
+
+  // A trailing '*' has no effect, and it is compatible with '$'.
+  CheckRobotsRules("/baz", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/bazfoo", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/bop", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/bopfoo", RobotsRulesParser::CheckResult::kAllowed);
+
+  // Patterns with leading '*' are not anchored to the beginning of the string.
+  CheckRobotsRules("/startwithstar", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/ZZZstartwithstar",
+                   RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/ZZZstartwithstarbar",
+                   RobotsRulesParser::CheckResult::kAllowed);
+
+  // Patterns with leading '*' correctly interact with trailing '$'.
+  CheckRobotsRules("/ZZZanchoredtoend",
+                   RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/ZZZanchoredtoendZZZ",
+                   RobotsRulesParser::CheckResult::kDisallowed);
+
+  VerifyTotalRobotsRulesApplyHistograms(19);
+}
+
+TEST_F(SubresourceRedirectRobotsRulesParserTest,
        TestInvalidatePendingRequests) {
   auto receiver1 = CheckRobotsRulesAsync("/allowed.jpg", 1 /*render_frame_id*/);
   auto receiver2 =
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4f80a29..8ac7466d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5071,6 +5071,7 @@
     sources += [
       "../browser/apps/app_service/app_platform_metrics_service_unittest.cc",
       "../browser/apps/app_service/intent_util_unittest.cc",
+      "../browser/apps/app_service/webapk/webapk_install_task_unittest.cc",
       "../browser/component_updater/cros_component_installer_chromeos_unittest.cc",
       "../browser/component_updater/metadata_table_chromeos_unittest.cc",
       "../browser/enterprise/reporting/android_app_info_generator_unittest.cc",
diff --git a/chrome/test/chromedriver/element_util.cc b/chrome/test/chromedriver/element_util.cc
index 767567a5..8e2f1cd 100644
--- a/chrome/test/chromedriver/element_util.cc
+++ b/chrome/test/chromedriver/element_util.cc
@@ -450,7 +450,7 @@
   if (status.IsError())
     return status;
   std::unique_ptr<base::Value> element_dict(CreateElement(element_id));
-  *is_focused = result->Equals(element_dict.get());
+  *is_focused = *result == *element_dict;
   return Status(kOk);
 }
 
diff --git a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/manifest.json b/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/manifest.json
index 607ba01..907edc31 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/manifest.json
+++ b/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/manifest.json
@@ -4,7 +4,8 @@
   "manifest_version": 2,
   "description": "Tests the webNavigation API events - clientRedirect.",
   "background": {
-    "page": "test_clientRedirect.html"
+    "scripts": ["test_clientRedirect.js"],
+    "persistent": true
   },
   "permissions": ["webNavigation", "tabs"]
 }
diff --git a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.html b/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.html
deleted file mode 100644
index dd27a43..0000000
--- a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<script src="_test_resources/api_test/webnavigation/framework.js"></script>
-<script src="test_clientRedirect.js"></script>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.js b/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.js
index ad878ad..16e1897 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.js
+++ b/chrome/test/data/extensions/api_test/webnavigation/clientRedirect/test_clientRedirect.js
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-onload = async function() {
+const scriptUrl = '_test_resources/api_test/webnavigation/framework.js';
+let loadScript = chrome.test.loadScript(scriptUrl);
+
+loadScript.then(async function() {
   let getURL = chrome.extension.getURL;
   let tab = await promise(chrome.tabs.create, {"url": "about:blank"});
   chrome.test.runTests([
@@ -83,4 +86,4 @@
       chrome.tabs.update(tab.id, { url: getURL('a.html') });
     },
   ]);
-};
+});
diff --git a/chrome/test/data/extensions/api_test/webnavigation/crash/manifest.json b/chrome/test/data/extensions/api_test/webnavigation/crash/manifest.json
index 169905a..48046732 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/crash/manifest.json
+++ b/chrome/test/data/extensions/api_test/webnavigation/crash/manifest.json
@@ -4,7 +4,8 @@
   "manifest_version": 2,
   "description": "Tests the webNavigation API events - crash.",
   "background": {
-    "page": "test_crash.html"
+    "scripts": ["test_crash.js"],
+    "persistent": true
   },
   "permissions": ["webNavigation", "tabs"]
 }
diff --git a/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.html b/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.html
deleted file mode 100644
index 4bfe706..0000000
--- a/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<script src="_test_resources/api_test/webnavigation/framework.js"></script>
-<script src="test_crash.js"></script>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.js b/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.js
index cfc8c746..faef79a3 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.js
+++ b/chrome/test/data/extensions/api_test/webnavigation/crash/test_crash.js
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-onload = async function() {
+const scriptUrl = '_test_resources/api_test/webnavigation/framework.js';
+let loadScript = chrome.test.loadScript(scriptUrl);
+
+loadScript.then(async function() {
   let tab = await promise(chrome.tabs.create, {"url": "about:blank"});
   let config = await promise(chrome.test.getConfig);
   let port = config.testServer.port;
@@ -91,4 +94,4 @@
       chrome.test.notifyPass();
     },
   ]);
-};
+});
diff --git a/chrome/test/data/extensions/api_test/webnavigation/failures/manifest.json b/chrome/test/data/extensions/api_test/webnavigation/failures/manifest.json
index 21955e6696..9e670e48 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/failures/manifest.json
+++ b/chrome/test/data/extensions/api_test/webnavigation/failures/manifest.json
@@ -4,7 +4,8 @@
   "manifest_version": 2,
   "description": "Tests the webNavigation API events - failures.",
   "background": {
-    "page": "test_failures.html"
+    "scripts": ["test_failures.js"],
+    "persistent": true
   },
   "permissions": ["webNavigation", "tabs"]
 }
diff --git a/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.html b/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.html
deleted file mode 100644
index 7858cf5..0000000
--- a/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<script src="_test_resources/api_test/webnavigation/framework.js"></script>
-<script src="test_failures.js"></script>
diff --git a/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.js b/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.js
index 104c09f..dad2ff4 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.js
+++ b/chrome/test/data/extensions/api_test/webnavigation/failures/test_failures.js
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-onload = async function() {
+const scriptUrl = '_test_resources/api_test/webnavigation/framework.js';
+let loadScript = chrome.test.loadScript(scriptUrl);
+
+loadScript.then(async function() {
   var getURL = chrome.extension.getURL;
   let tab = await promise(chrome.tabs.create, {"url": "about:blank"});
 
@@ -226,4 +229,4 @@
       chrome.tabs.update(tab.id, { url: getURL('nonexistent.html') });
     },
   ]);
-};
+});
diff --git a/chrome/test/webapps/.style.yapf b/chrome/test/webapps/.style.yapf
new file mode 100644
index 0000000..3d8b70f
--- /dev/null
+++ b/chrome/test/webapps/.style.yapf
@@ -0,0 +1,2 @@
+[style]
+based_on_style = pep8
\ No newline at end of file
diff --git a/chrome/test/webapps/OWNERS b/chrome/test/webapps/OWNERS
new file mode 100644
index 0000000..71423ff
--- /dev/null
+++ b/chrome/test/webapps/OWNERS
@@ -0,0 +1,8 @@
+dmurph@chromium.org
+cmp@chromium.org
+phillis@chromium.org
+jarrydg@chromium.org
+huangdarwin@chromium.org
+estade@chromium.org
+mattm@chromium.org
+msw@chromium.org
diff --git a/chrome/test/webapps/README.md b/chrome/test/webapps/README.md
new file mode 100644
index 0000000..ee4109e9
--- /dev/null
+++ b/chrome/test/webapps/README.md
@@ -0,0 +1,145 @@
+# Desktop Web App Integration Testing Framework
+
+## Future work
+
+* The framework test enumeration given the graph could be slightly improved to reduce the final tests. Extra tests can be created if there is a branch-and-rejoin in the graph, but thankfully not exponentially.
+* The algorithm in `generate_framework_tests_and_coverage.py` file could be improved by not re-reading files and re-generating graphs. One way to do this is implement deep copying for the graph, so these can be saved & re-used in later iterations.
+* Using the action name to infer `state_check` and `internal` properties. `state_check` actions should start with `assert_`, and `internal` actions should end with `_internal`.
+* Change "action parameters" to "action modes".
+
+## Background
+
+The WebAppProvider system has very wide action space and testing state interactions between all of the subsystems is very difficult. The goal of this piece is to accept:
+* A list of action-based tests which fully test the WebAppProvider system (required-coverage tests), and
+* A list of actions supported by the integration test framework (per-platform),
+and output
+* a minimal number of tests (per-platform) for the framework to execute that has the most coverage possible of the original list of tests, and
+* the resulting coverage of the system (with required-coverage tests as 100%).
+
+This is done by downloading [data](data/) from a [google sheet](https://docs.google.com/spreadsheets/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml), processing it, and saving the results in the [output/](output/) folder.
+
+See the [design doc](https://docs.google.com/document/d/e/2PACX-1vTFI0sXhZMvvg1B3sctYVUe64WbLVNzuXFUa6f3XyYTzKs2JnuFR8qKNyXYZsxE-rPPvsq__4ZCyrcS/pub) for more information and links.
+
+## Terminology
+
+* **Action** - A primitive test operation, or test building block, that can be used to create coverage tests. The integration test framework may or may not support an action on a given platform.
+* **Test** - A sequence of actions used to test the WebAppProvider system.
+* **'State Check' action** - Action that does not change any system state, and instead just inspects the state to ensure it is as expected (e.g. `assert_install_icon_shown`).
+* **Full & Partial coverage** - this describes the way that integration tests may test the system. If all actions in the coverage tests are supported by the framework, then the framework will be able to 'fully' cover the required coverage tests. However, this is not always the case, and to help compensate for un-automatable actions, the framework supports adding 'partial coverage' nodes or paths to the graph. This allows the testing framework to execute a full test even if it couldn't fully cover an action in the middle of the test.
+
+## Understanding and Implementing Test Cases
+
+Actions are the basic building blocks of integration tests. A test is a sequence of actions. Each action has a name that must be a valid C++ identifier.
+
+Actions are defined (and can be modified) in [this](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=1864725389) sheet. Tests are defined (and can be modified) in [this](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=2008870403) sheet. Partial coverage path additions are defined (and can be modified) in [this](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=452077264) sheet.
+
+### Action Creation & Specification
+
+[Actions](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=1864725389) are the building blocks of tests.
+
+#### Templates
+To help making test writing less repetitive, actions are described as templates in the [actions](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=1864725389) spreadsheet. Action templates specify actions while avoiding rote repetition. Each action template has a name (the **action base name**). Each action template supports at most one parameter, which takes values from a predefined list associated with the template. Parameter values must also be valid C++ identifiers.
+
+An action template without parameters specifies one action whose name matches the template. For example, the `assert_window_closed` template generates the `assert_window_closed` action.
+
+An action template with a parameter that can take N values specifies N actions, whose names are the concatenations of the template name and the corresponding value name, separated by an underscore (_). For example, the `clear_app_badge` template generates the `clear_app_badge_site_a` and `clear_app_badge_site_b` actions.
+
+#### Default Values
+
+All parametrized templates can mark one of their parameter values as the default value. This value is used to magically convert template names to action names.
+
+#### Internal Actions
+The testing framework is not going to be able to test every single manual action. To help allow partial coverage of these actions, internal actions are often specified, and then used in the partial coverage file, to allow the framework to execute the full test still. These are generally signified by postfixing `_internal` on the action base name.
+
+#### Specifying a Parameter
+Human-friendly action names are a slight variation of the canonical names above.
+
+Actions generated by parameter-less templates have the same human-friendly name as their (canonical?) name.
+
+Actions generated by parametrized templates use parenthesis to separate the template name from the value name. For example, the actions generated by the `clear_app_badge` template have the human-friendly names `clear_app_badge(site_a)` and `clear_app_badge(site_b)`.
+
+The template name can be used as the human-friendly name of the action generated by the template with the default value. For example, `clear_app_badge` is a shorter human-friendly name equivalent to `clear_app_badge(site_a)`.
+
+### Test Creation & Specification
+
+[Tests]((https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=2008870403)) are created specifying actions, and they are currently organized by **affected-by** edges.
+
+#### Affected-by edges
+
+This framework is designed to consume an exhaustive list of **affected-by** action edges. These are a pair of two actions, where the first action affects the second. For example, the action `set_app_badge` will affect the action `assert_app_badge_has_value`. Or, `uninstall_from_app_list` will affect `assert_platform_shortcut_exists`.
+
+Once these edges are identified, tests can be created around them.
+
+#### Creating a test
+
+Given an **affected-by** edge, a test should be created that does the bare minimum necessary to set up the test before the first affected-by action, and then any required other actions in between that and the second affect-by action.
+
+The framework is designed to be able to collapse tests that contain common non-'state-check' actions, so adding a new test does not always mean that a whole new test will be run by the framework. Sometimes it only adds a few extra state-check actions in an existing test.
+
+#### Unsupportable actions & partial coverage
+
+Some actions might be unsupportable by an automated test framework. For example, `clear_app_badge` cannot work as a browsertest action, as it (currently)  a level of OS integration that cannot happen if many browsertests are all happening at the same time. Thus, to still partially cover test cases that use this action, a `clear_app_badge_internal` action exists which hooks into a fake interface to track badging actions for a webapp. The framework can use this instead, and the test creator then has to specify this in the partial coverage file.
+
+The [partial coverage](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=452077264) sheet specifies when user actions that cannot be automated can be replaced by one or more internal actions. For example, the actions `list_apps, uninstall_from_app_list` can be partially covered by the internal action `uninstall_internal`. When those two actions are encountered during test generation, the script knows that, if needed, the action `unintall_internal` can be used if the framework does not support the first two actions.
+
+## Script Usage
+
+### Downloading test data
+
+The test data is hosted in this [spreadsheet](https://docs.google.com/spreadsheets/d/1d3iAOAnojp4_WrPky9exz1-mjkeulOJVUav5QYG99MQ/edit). To download the latest copy of the data, run the included script:
+```sh
+./chrome/test/web_apps/download_data_from_sheet.sh
+```
+
+This will download the data from the sheet into csv files in the [data/](data/) directory:
+
+* `actions.csv` This lists all actions that can be used, and specifies if the action is an 'state_check' action (which means it doesn't effect state). The first row of this file is skipped.
+* `automated_tests.csv` This lists the audited Web App automated tests & their approximate actions. First column and row are skipped.
+* `manual_tests.csv` This lists the audited manual tests & their approximate actions. First column and row are skipped.
+* `coverage_required.csv` This is the full list of all tests needed to fully cover the Web App system. The first two rows are skipped.
+* `framework_actions_*.csv` These are the supported actions by the browsertest testing framework for the given platform. First row is skipped.
+* `partial_coverage_paths.csv` These are the partial coverage paths that can be use to augment the action graph. These help us add and use internal actions to partial cover tests that have actions that are hard or impossible to support in the automated framework.
+
+The only data that is NOT downloaded is the supported framework action files (e.g. [framework_actions_linux.csv](data/framework_actions_linux.csv)), which should be modified directly in the Chromium tree, in the same CL that implements (or removes) the supported action in the framework.
+
+### Generating test descriptions & coverage
+
+Test descriptions for each platform are generated by running:
+```sh
+chrome/test/web_apps/generate_framework_tests_and_coverage.py
+```
+This uses the files in `chrome/test/web_apps/data` to generate the following in `chrome/test/web_apps/output`:
+
+* Per-platform coverage `tsv` files that contain a copy of the `coverage_required.tsv` file, but with markers per action to allow a conditional formatter (like the one [here](https://docs.google.com/spreadsheets/d/1d3iAOAnojp4_WrPky9exz1-mjkeulOJVUav5QYG99MQ/edit#gid=884228058)) to highlight what was and was not covered by the testing framework.
+  * These files also contain a coverage % at the top of the file. Full coverage is the percent of the input actions that were covered by the tests. Partial coverage is the percent of input actions plus any 'partial paths' that allow partial coverage of an unsupported action
+* Per-platform framework test definition (e.g. `framework_tests_cros.csv`) that can be used by the Web App integration test browsertest framework.
+  * Actions involving the sync system require a framework specialization, so these tests are stored in a separate file (e.g. `framework_tests_sync_cros.csv`).
+
+#### Audited coverage information (and how to exclude this information)
+
+The coverage numbers include coverage from the audited [automated](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=1894585254) and [manual](https://docs.google.com/spreadsheets/u/1/d/e/2PACX-1vSbO6VsnWsq_9MN6JEXlL8asMqATHc2-pz9ed_Jlf5zHJGg2KAtegsorHqkQ5kydU6VCqebv_1gUCD5/pubhtml?gid=1424278080) test data. This was generated by auditing all existing automated tests in the codebase and all manual tests owned by the QA team. All tests that were supported by this framework were translated and recorded.
+
+To exclude this information in the coverage analysis, you can specify the `--ignore_audited` flag like so:
+```sh
+chrome/test/web_apps/generate_framework_tests_and_coverage.py --ignore_audited
+```
+
+### Exploring the tested and coverage graphs
+
+To view the directed graphs that are generated to process the test and coverage data, the `--graphs` switch can be specified:
+```sh
+chrome/test/web_apps/generate_framework_tests_and_coverage.py --graphs
+```
+
+This will generate:
+* `coverage_required_graph.dot` - The graph of all of the required test coverage. Green nodes are actions explicitly listed in the coverage list, and orange nodes specify partial coverage paths.
+* `framework_test_graph_<platform>.dot` - The graph that is now tested by the generated framework tests for the given platform, including partial coverage.
+* `coverage_graph_<platform>.dot` - The graph of the coverage provided by the generated framework tests and audited tests (see above about how to exclude audited tests).
+
+The [graphviz](https://graphviz.org/) library can be used to view these graphs. An install-free online version is [here](https://dreampuf.github.io/GraphvizOnline/).
+
+### Debugging further
+
+To help debug or explore further, please see the [`graph_cli_tool.py`](graph_cli_tool.py) script, which contains the actual algorithms, and also includes a number of command line utilities to process the various files.
+
+Both this file and the [`generate_framework_tests_and_coverage.py`](generate_framework_tests_and_coverage.py) file support the `-v` option to print out informational logging.
diff --git a/chrome/test/webapps/__init__.py b/chrome/test/webapps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/test/webapps/__init__.py
diff --git a/chrome/test/webapps/classes.py b/chrome/test/webapps/classes.py
new file mode 100755
index 0000000..b47534f0
--- /dev/null
+++ b/chrome/test/webapps/classes.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Classes used to process the Web App testing framework data.
+"""
+
+from enum import Enum
+from enum import unique
+
+
+@unique
+class ActionCoverage(Enum):
+    PARTIAL = 1
+    FULL = 2
+    NONE = 3
+
+
+class Action:
+    """Represents a user action.
+
+    Attributes:
+        name: The name of the action often can contain a parameter, which is
+              resolved at parse time.
+        base_name: If the action has parameters, this is the base action name
+                   before the parameter was concatenated, resulting in the name.
+        is_state_check: If the action only inspects state, and does not change
+                       state.
+    """
+
+    def __init__(self, name: str, base_name: str, is_state_check: bool):
+        assert name is not None
+        assert base_name is not None
+        assert is_state_check is not None
+        self.name = name
+        self.base_name = base_name
+        self.is_state_check = is_state_check
+
+    def __str__(self):
+        return (f"Action[{self.name!r}, "
+                f"base_name: {self.base_name!r}, "
+                f"state_check: {self.is_state_check!r}]")
+
+
+class ActionNode:
+    """Represents a node in an action graph, where all nodes are
+    non-state_check actions.
+
+    Attributes:
+        action: The action this node represents.
+        coverage: The test coverage of this node.
+        children: The children of this node, keyed by action name.
+        parents: The parents of this node, keyed by action name.
+        full_coverage_tests: The tests that this node helps fully cover.
+        partial_coverage_tests: The tests that this node helps partially cover.
+        state_check_actions: State checks that can be performed on this node.
+        state_check_actions_coverage: Coverage of the given state_check action.
+        graph_id: Used for graphviz file generation.
+        dead: Used for debugging / assertions. Records the replacement node if
+              this one is dead.
+        keep: Used for graph pruning to signify this node should be kept.
+    """
+
+    def __init__(self, action: Action):
+        assert action is not None
+        assert not action.is_state_check
+        self.action = action
+        self.coverage = None
+        self.children = {}
+        self.parents = {}
+        self.full_coverage_tests = set()
+        self.partial_coverage_tests = set()
+        self.state_check_actions = {}
+        self.state_check_actions_coverage = {}
+        self.graph_id = None
+        self.dead = None
+        self.keep = False
+
+    def AssertChildrenValidity(self):
+        assert not self.dead, self.GetGraphPathStr()
+        for child in self.children.values():
+            assert self.action.name in child.parents, (self.GetGraphPathStr() +
+                                                       "\nChild:\n" +
+                                                       child.GetGraphPathStr())
+            assert child.parents[self.action.name] == self, (
+                "Me:\n" + self.GetGraphPathStr() + "\nChild:\n" +
+                child.GetGraphPathStr() + "\nChild thinks I am:\n" +
+                child.parents[self.action.name].GetGraphPathStr())
+            assert child is not self
+            for parent in child.parents.values():
+                assert not parent.dead
+                assert not parent.children[child.action.name].dead
+                assert parent.children[child.action.name] == child
+            child.AssertChildrenValidity()
+
+    def AssertParentValidity(self):
+        assert not self.dead, self.GetGraphPathStr()
+        for parent in self.parents.values():
+            assert parent.children[self.action.name] == self, (
+                "Me:\n" + self.GetGraphPathStr() + "\nParent:\n" +
+                parent.GetGraphPathStr() + "\nParent thinks I am:\n" +
+                parent.children[self.action.name].GetGraphPathStr())
+            assert parent.children[self.action.name] == self
+            assert parent is not self, parent.action.name
+            for child in parent.children.values():
+                assert child.parents[parent.action.name] == parent
+                assert not child.parents[parent.action.name].dead
+            parent.AssertParentValidity()
+
+    def AssertValidity(self):
+        self.AssertChildrenValidity()
+        self.AssertParentValidity()
+
+    def HasChild(self, child: 'ActionNode') -> bool:
+        return (child.action.name in self.children
+                and self.children[child.action.name] is child
+                and self.action.name in child.parents
+                and child.parents[self.action.name] is self)
+
+    def AddChild(self, child: 'ActionNode'):
+        assert child is not self
+        assert not self.dead
+        assert not child.dead
+        assert not self.HasChild(child), (child.action.name + " already in " +
+                                          self.action.name)
+        assert self.action.name not in child.parents, (self.action.name +
+                                                       " already parent of " +
+                                                       child.action.name)
+        self.children[child.action.name] = child
+        child.parents[self.action.name] = self
+
+    def RemoveChild(self, child: 'ActionNode'):
+        assert child is not self
+        assert not self.dead
+        assert not child.dead
+        assert child.action.name in self.children, (child.action.name +
+                                                    " not in " +
+                                                    self.action.name)
+        assert self.action.name in child.parents, (self.action.name +
+                                                   " not parent of " +
+                                                   child.action.name)
+        del self.children[child.action.name]
+        del child.parents[self.action.name]
+
+    def AddStateCheckAction(self, action: Action, coverage: ActionCoverage):
+        assert action is not None and isinstance(action, Action)
+        assert action.is_state_check
+        assert coverage is not None and isinstance(coverage, ActionCoverage)
+        self.state_check_actions[action.name] = action
+        if (action.name not in self.state_check_actions_coverage
+                or coverage is ActionCoverage.FULL):
+            self.state_check_actions_coverage[action.name] = coverage
+
+    def HasStateCheckAction(self, action: Action) -> bool:
+        assert action.is_state_check
+        return action.name in self.state_check_actions
+
+    def ResolveToAliveNode(self) -> 'ActionNode':
+        if not self.dead:
+            return self
+        return self.dead.ResolveToAliveNode()
+
+    def GetGraphvizLabel(self) -> str:
+        node_str = "< <B>" + self.action.name + "</B>"
+        if self.state_check_actions:
+            node_str += "<BR/>(" + ", ".join(
+                [action_name
+                 for action_name in self.state_check_actions]) + ")"
+        return node_str + " >"
+
+    def GetGraphPathStr(self) -> str:
+        if len(self.parents) == 0:
+            return self.action.name
+        if len(self.parents) == 1:
+            return (list(self.parents.values())[0].GetGraphPathStr() + " -> " +
+                    self.action.name)
+        parent_strs = []
+        for parent in self.parents.values():
+            parent_strs.append(parent.GetGraphPathStr())
+        return ("Branched: [" + " | ".join(parent_strs) + "] -> " +
+                self.action.name)
+
+    def __str__(self):
+        return (
+            f"ActionNode[{self.action.name!r}, "
+            f"coverage: {self.coverage!r}, "
+            f"children: {', '.join(self.children.keys())!r}, "
+            f"parents: {', '.join(self.parents.keys())!r}, "
+            f"full_coverage_tests: "
+            f"{', '.join([t.name for t in self.full_coverage_tests])!r}, "
+            f"partial_coverage_tests: "
+            f"{', '.join([t.name for t in self.partial_coverage_tests])!r}]")
+
+
+class PartialCoverageAddition:
+    """Represents a partial coverage path addition, where, if the input actions
+    are found, then the output actions are added as a new edge to the graph.
+
+    Attributes:
+        input_actions: Action sequence to look for in a directed action graph.
+        output_actions: Actions to augment the graph with if the input actions
+                        are found.
+    """
+
+    def __init__(self):
+        self.input_actions = []
+        self.output_actions = []
+
+    def Reverse(self):
+        temp = self.input_actions
+        self.input_actions = self.output_actions
+        self.output_actions = temp
+
+    def __str__(self):
+        return (f"PartialCoverageAddition[input_actions: "
+                f"{', '.join([a.name for a in self.input_actions])!r}, "
+                f"output_actions: "
+                f"{', '.join([a.name for a in self.output_actions])!r}]")
+
+
+class CoverageTest:
+    """Represents a test with a list of actions
+
+    Attributes:
+        name: Unique name, or identifier, of the test.
+        actions: list of actions the test specifies to execute.
+    """
+
+    def __init__(self, name):
+        assert name is not None
+        assert isinstance(name, str)
+        self.name = name
+        self.actions = []
+
+    def __str__(self):
+        return (f"CoverageTest[name: {self.name!r}, actions: "
+                f"{', '.join([a.name for a in self.actions])}]")
diff --git a/chrome/test/webapps/data/actions.csv b/chrome/test/webapps/data/actions.csv
new file mode 100644
index 0000000..4fc520d
--- /dev/null
+++ b/chrome/test/webapps/data/actions.csv
@@ -0,0 +1,122 @@
+# Action base name,Parameters (* = default parameter),State Check,ManualOnly,Status,Description
+add_policy_app_internal_tabbed_no_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,WIP,Internal method of adding a policy app - does not fully test the enterprise integration.
+add_policy_app_internal_tabbed_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Internal method of adding a policy app - does not fully test the enterprise integration.
+add_policy_app_internal_windowed_no_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,WIP,Internal method of adding a policy app - does not fully test the enterprise integration.
+add_policy_app_internal_windowed_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Internal method of adding a policy app - does not fully test the enterprise integration.
+add_policy_app_tabbed_no_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,"Add a force-installed enterprise policy app to the user profile (must be managed profile). tabbed, no platform shortcut"
+add_policy_app_tabbed_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Internal Impl,"Add a force-installed enterprise policy app to the user profile (must be managed profile). tabbed, with platform shortcut"
+add_policy_app_windowed_no_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,"Add a force-installed enterprise policy app to the user profile (must be managed profile). windowed, no platform shortcut"
+add_policy_app_windowed_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Internal Impl,"Add a force-installed enterprise policy app to the user profile (must be managed profile). windowed, with platform shortcut"
+assert_app_badge_empty,site_a* | site_b,Y,Y,Awaiting Platform Integration,Check that the 'badge' on the app icon is empty
+assert_app_badge_has_value,site_a* | site_b,Y,Y,Awaiting Platform Integration,Check that the 'badge' on the app icon has a value
+assert_app_badge_updated,site_a* | site_b,Y,Y,Awaiting Platform Integration,Check that the 'badge' on the app icon has changed values
+assert_app_in_list,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Internal Impl,
+assert_app_in_list_icon_correct,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Not Yet Implemented,Check that the icon for the given app in the app list is correct.
+assert_app_in_list_locally_installed,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Internal Impl,
+assert_app_in_list_not_locally_installed,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Internal Impl,Check that the given app is in the app list and is not installed (greyed out icon)
+assert_app_in_list_not_windowed,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Not Yet Implemented,"Check that the given app is in the list, and launches in a browser tab"
+assert_app_in_list_windowed,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Not Yet Implemented,"Check that the given app is in the list, and launches in a dedicated window"
+assert_app_installed_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Abandoned,
+assert_app_list_empty,,Y,,Not Yet Implemented,The app list is empty (no apps)
+assert_app_locally_installed_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,WIP,
+assert_app_not_in_list,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,Check that the given app is NOT in the app list
+assert_app_not_installed_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Abandoned,
+assert_app_not_locally_installed_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,
+assert_correct_window_title,,Y,,Not Yet Implemented,Check that the window title is correct
+assert_create_shortcut_not_shown,,Y,,Not Yet Implemented,"Check that the ""Create Shortcut"" menu option (3-dot->""More Tools""->""Create Shortcut) is greyed out"
+assert_create_shortcut_shown,,Y,,Not Yet Implemented,"Check that the ""Create Shortcut"" menu option (3-dot->""More Tools""->""Create Shortcut) is shown"
+assert_icon_correct_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Not Yet Implemented,internal-only
+assert_install_icon_not_shown,,Y,,Implemented,"Check that the ""Install"" icon in the omnibox is not shown"
+assert_install_icon_shown,,Y,,Implemented,"Check that the ""Install"" icon in the omnibox is shown"
+assert_installable,,Y,,Implemented,
+assert_launch_icon_not_shown,,Y,,Implemented,
+assert_launch_icon_shown,,Y,,Implemented,
+assert_manifest_display_mode_browser_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,internal-only
+assert_manifest_display_mode_minimal_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,WIP,internal-only
+assert_manifest_display_mode_standalone_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,internal-only
+assert_navigation_start_url,,Y,,Not Yet Implemented,
+assert_no_toolbar,,Y,,Not Yet Implemented,
+assert_not_installable_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Not Yet Implemented,Internal evaluation of whether a site is installable or not.
+assert_old_chrome_version,84*,Y,,Not Yet Implemented,
+assert_platform_shortcut_created,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,Y,Awaiting Platform Integration,The desktop platform shortcut has been removed.
+assert_platform_shortcut_exists,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,Y,Awaiting Platform Integration,The desktop platform shortcut exists.
+assert_platform_shortcut_icon_correct,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,Y,Awaiting Platform Integration,The icon of the platform shortcut (on the desktop) is correct
+assert_platform_shortcut_not_exists,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,Y,Awaiting Platform Integration,The desktop platform shortcut has been removed.
+assert_platform_shortcut_right_click_menu_has_actions,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,Y,Awaiting Platform Integration,"Check that the platform shortcut, when right clicked, displays actions that the user can click on"
+assert_scope_correct_internal_site_a,site_a* | site_a_foo | site_a_bar,Y,,Not Yet Implemented,
+assert_scope_correct_internal_site_a_foo,site_a* | site_a_foo | site_a_bar,Y,,Not Yet Implemented,
+assert_tab_created,,Y,,Implemented,A tab was created in a chrome browser window
+assert_theme_color,site_a* | site_a_foo | site_a_bar | site_b,Y,,Not Yet Implemented,Asserts that the theme color of the given app window is correct.
+assert_theme_color_internal,site_a* | site_a_foo | site_a_bar | site_b,Y,,Not Yet Implemented,Asserts that the theme color of the given app window is correct.
+assert_toolbar,,Y,,Not Yet Implemented,Check that the PWA window has a custom toolbar to show the out-of-scope url.
+assert_user_display_mode_browser_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,internal-only
+assert_user_display_mode_standalone_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,Y,,Implemented,internal-only
+assert_window_closed,,Y,,Implemented,The window was closed
+assert_window_color_correct,,Y,,Not Yet Implemented,The color of the window has changed.
+assert_window_created,,Y,,Implemented,A window was created.
+assert_window_display_minimal,,Y,,Implemented,"Check that the window is a PWA window, and has minimal browser controls."
+assert_window_display_standalone,,Y,,Implemented,"Check that the window is a PWA window, and has no browser controls."
+assert_window_icon_correct,,Y,,Not Yet Implemented,
+assert_window_title,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,
+chrome_update,,,,Not Yet Implemented,Update chrome from the past to the present
+chrome_update_with_bmo_migration,,,,Abandoned,Update chrome from pre-m85 to post-m85
+clear_app_badge,site_a* | site_b,,,Awaiting Platform Integration,The WebApp clears the 'badge' value from it's icon
+close_browser,,,,Abandoned,Close the browser window.
+close_custom_toolbar,,,,Not Yet Implemented,Press the 'x' button on the custom toolbar that is towards the top of the WebApp window.
+close_pwa,,,,Implemented,Close the WebApp window.
+copy_pwa_url,,,,Not Yet Implemented,"Click the ""Copy URL"" option in the apps window's 3 dot menu"
+create_shortcuts,,,,Not Yet Implemented,create shortcut in app list
+delete_platform_shortcut,,,,Awaiting Platform Integration,Delete the shortcut that lives on the operating system
+delete_profile,,,,Not Yet Implemented,Delete the user profile.
+drag_url_to_apps_list,,,,Awaiting Platform Integration,"Open chrome://apps in a separage window, and drag a url (highlight & drag url from the omnibox) to the chrome://apps page"
+force_manifest_update_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,WIP,Force chrome to process a manifest update for a web app.
+install_create_shortcut_tabbed,,,,Implemented,Menu -> Create Shortcuts -> Uncheck 'open as window'
+install_create_shortcut_windowed,,,,Implemented,Menu -> Create Shortcuts -> check 'open as window'
+install_internal_tabbed,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,"Installation using internal API, which skips a lot of the UI and user code."
+install_internal_windowed,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,"Installation using internal API, which skips a lot of the UI and user code."
+install_locally,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Internal Impl,"Complete installation of the not-locally-installed app by right-clicking on the grey icon in the app listing and selecting ""Install"""
+install_locally_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,"Complete installation of the not-locally-installed app by right-clicking on the grey icon in the app listing and selecting ""Install"""
+install_omnibox_or_menu,,,,Implemented,"Install the WebApp by clicking on the install icon in the omnibox, or ""Install _"" in the 3-dot menu."
+install_using_omnibox_icon,,,,Not Yet Implemented,
+launch_from_list,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,Launch the app from the app list
+launch_from_omnibox_or_menu,,,,Not Yet Implemented,"Launch by using the 'launch' icon in the omnibox, or from the 3-dot menu entry."
+launch_from_platform_shortcut,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,Lauch an app from a platform shortcut on the user's desktop or start menu.
+launch_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Launching using internal API
+list_apps,,,,Not Yet Implemented,"e.g. go to chrome://apps on non-ChromeOS, or app list on ChromeOS"
+list_apps_internal,,,,Implemented,
+manifest_update_colors,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,The website updates it's manifest.json to change the 'theme' color
+manifest_update_display_browser,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,Updates the display property of the manifest to 'browser' on the given site's manifest
+manifest_update_display_minimal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Updates the display property of the manifest to 'minimal' on the given site's manifest
+manifest_update_display_standalone,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Abandoned,Updates the display property of the manifest to 'standalone' on the given site's manifest
+manifest_update_icons,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,Updates the icon field in the manifest of the website.
+manifest_update_scope_site_a,site_a* | site_a_foo | site_a_bar,,,Implemented,"Update the scope of site a, to the given scope"
+manifest_update_scope_site_a_foo,site_a* | site_a_foo | site_a_bar,,,Abandoned,"Update the scope of site a/foo/, to the given scope"
+navigate_browser_in_scope,site_a* | site_a_foo | site_a_bar | site_b,,,Implemented,Navigate the browser to a website that is part of a PWA (e.g. twitter.com)
+navigate_browser_out_scope,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Navigate the browser to a website that is NOT part of the PWA (e.g. twitter.com)
+navigate_crashed_url,,,,Not Yet Implemented,"Navigate to a page that crashes, or simulates a crashed renderer. chrome://crash"
+navigate_installable,site_a* | site_a_foo | site_a_bar | site_b,,,Implemented,"Specifies the scope. site_a: https://example.com/, site_a_foo: https://example.com/foo,  site_a_bar: https://example.com/bar"
+navigate_link_target_blank,,,,Not Yet Implemented,"Click on a href link on the current page, where the target of the link is ""_blank"""
+navigate_not_installable,site_c*,,,Implemented,Navigate to a URL that is NOT installable.
+navigate_notfound_url,,,,WIP,Navigate to a url that returns a 404 server error.
+navigate_pwa_in_scope,site_a* | site_a_foo | site_a_bar | site_b,,,Not Yet Implemented,"Navigate the webapp to a url that is in-scope of the webapp (same origin, basically)"
+navigate_pwa_out_scope,site_a* | site_a_foo | site_a_bar | site_b | external,,,Not Yet Implemented,"Navigate the webapp to a url that is out-of-scope of the webapp (different origin, basically)"
+navigate_to_installable,site_a* | site_a_foo | site_a_bar | site_b,,,Not Yet Implemented,
+open_in_chrome,,,,Not Yet Implemented,Click on the 'open in chrome' link in the 3-dot menu of the app window
+remove_policy_app,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,Remove a force-installed policy app to the user profile (must be managed profile)
+remove_policy_app_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Abandoned,
+restart_chrome_for_manifest_update,site_a* | site_a_foo | site_a_bar | site_b,,,Abandoned,Restart chrome
+set_app_badge,site_a* | site_b,,,Awaiting Platform Integration,Set the app badge for the given site to a value.
+set_open_in_tab,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Internal Impl,"Uncheck the ""open in window"" checkbox in the right-click menu of the app icon, in the app list page"
+set_open_in_tab_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,
+set_open_in_window,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Internal Impl,"Check the ""open in window"" checkbox in the right-click menu of the app icon, in the app list page"
+set_open_in_window_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,
+switch_incognito_profile,,,,Not Yet Implemented,Switch to using incognito mode
+switch_profile_clients,user_a_client_2* | user_a_client_1,,,Implemented,Switch to a different instance of chrome signed in to the same profile
+sync_turn_off,,,,Implemented,"Turn chrome sync off for ""Apps"": chrome://settings/syncSetup/advanced"
+sync_turn_on,,,,Implemented,"Turn chrome sync on for ""Apps"": chrome://settings/syncSetup/advanced"
+uninstall_from_app_list,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Not Yet Implemented,Uninstall the webapp from the app list.
+uninstall_from_menu,,,,Implemented,Uninstall the webapp from the 3-dot menu in the webapp window
+uninstall_internal,site_a* | site_a_foo | site_a_bar | site_b | site_c,,,Implemented,
+uninstall_windows_control_panel,site_a* | site_a_foo | site_a_bar | site_b | site_c,,Y,Abandoned,Uninstall the webapp through the windows control panel (not implemented yet).
+use_pwa_window_controls,,,,Not Yet Implemented,Verify that web app window controls work
+user_signin,,,,Implemented,Sign in to chrome  / chrome sync
\ No newline at end of file
diff --git a/chrome/test/webapps/data/automated_tests.csv b/chrome/test/webapps/data/automated_tests.csv
new file mode 100644
index 0000000..f1f5159
--- /dev/null
+++ b/chrome/test/webapps/data/automated_tests.csv
@@ -0,0 +1,434 @@
+# Automated test,Actions (Bold rows are examples of cases that should be included in testing journies),,,,,,,,,,,,,,
+All/HostedAppTest.NotWebApp/0,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.CanUserUninstall/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.CanUserUninstall/WebApp,install_internal_windowed,launch_internal,,,,,,,,,,,,,
+All/HostedOrWebAppTest.CtrlClickLink/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.CtrlClickLink/WebApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.OpenLinkInNewTab/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.OpenLinkInNewTab/WebApp,N/A,Tests ctrl-clicking a link in an app opens a new tab in the browser,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBar/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBar/WebApp,install_internal_windowed,launch_internal,assert_no_toolbar,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForAppWithoutWWW/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForAppWithoutWWW/WebApp,install_internal_windowed,launch_internal,assert_no_toolbar,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPAppHTTPSUrl/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPAppHTTPSUrl/WebApp,install_internal_windowed,launch_internal,assert_no_toolbar,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPAppSameOrigin/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPAppSameOrigin/WebApp,install_internal_windowed,launch_internal,assert_no_toolbar,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPSAppHTTPUrl/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPSAppHTTPUrl/WebApp,install_internal_windowed,launch_internal,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPSAppSameOrigin/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.ShouldShowCustomTabBarForHTTPSAppSameOrigin/WebApp,N/A,"Tests that an http-installed app, going to the http start_url, still shows a toolbar (insecure)",,,,,,,,,,,,,
+All/HostedOrWebAppTest.SubframeRedirectsToHostedApp/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.SubframeRedirectsToHostedApp/WebApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsOpenApplication/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsOpenApplication/WebApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsOpenInChrome/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsOpenInChrome/WebApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsReparentWebContents/HostedApp,N/A,,,,,,,,,,,,,,
+All/HostedOrWebAppTest.WebContentsPrefsReparentWebContents/WebApp,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckChangeInIconDimensions/0,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckChangeInIconDimensions/1,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckChangeInIconDimensions/2,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckChangeInIconDimensions/3,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckLazyServiceWorkerPassesWhenWaiting/0,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckLazyServiceWorkerPassesWhenWaiting/1,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckLazyServiceWorkerPassesWhenWaiting/2,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckLazyServiceWorkerPassesWhenWaiting/3,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckPageWithNestedServiceWorkerCanBeInstalled/0,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckPageWithNestedServiceWorkerCanBeInstalled/1,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckPageWithNestedServiceWorkerCanBeInstalled/2,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckPageWithNestedServiceWorkerCanBeInstalled/3,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckServiceWorkerErrorIsNotCached/0,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckServiceWorkerErrorIsNotCached/1,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckServiceWorkerErrorIsNotCached/2,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckServiceWorkerErrorIsNotCached/3,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckWebapp/0,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckWebapp/1,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckWebapp/2,N/A,,,,,,,,,,,,,,
+All/InstallableManagerOfflineCapabilityBrowserTest.CheckWebapp/3,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkClick/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkClick/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkContextMenuNewTab/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkContextMenuNewTab/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkContextMenuNewWindow/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.AnchorLinkContextMenuNewWindow/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.CaptureToOpenedWindowAndNavigateURL/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.CaptureToOpenedWindowAndNavigateURL/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.ChangeLocationHref/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.ChangeLocationHref/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.OmniboxPasteAndGo/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.OmniboxPasteAndGo/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.OmniboxTypeURLAndNavigate/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.OmniboxTypeURLAndNavigate/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.WindowOpen/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.WindowOpen/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.WindowOpenFromOtherSWA/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppLinkCaptureBrowserTest.WindowOpenFromOtherSWA/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerAdditionalSearchTermsTest.AdditionalSearchTerms/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerAdditionalSearchTermsTest.AdditionalSearchTerms/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerChromeUntrustedTest.Install/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerChromeUntrustedTest.Install/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerFileHandlingOriginTrialsBrowserTest.FileHandlingWorks/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerFileHandlingOriginTrialsBrowserTest.FileHandlingWorks/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerFileHandlingOriginTrialsBrowserTest.PRE_FileHandlingWorks/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerFileHandlingOriginTrialsBrowserTest.PRE_FileHandlingWorks/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.LaunchDirectoryForSystemWebApp/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.LaunchDirectoryForSystemWebApp/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.ReadWritePermissions_OrdinaryDirectory/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.ReadWritePermissions_OrdinaryDirectory/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.ReadWritePermissions_SensitiveDirectory/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchDirectoryBrowserTest.ReadWritePermissions_SensitiveDirectory/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchFilesBrowserTest.LaunchFilesForSystemWebApp/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerLaunchFilesBrowserTest.LaunchFilesForSystemWebApp/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerNotShownInLauncherTest.NotShownInLauncher/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerNotShownInLauncherTest.NotShownInLauncher/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerNotShownInSearchTest.NotShownInSearch/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerNotShownInSearchTest.NotShownInSearch/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_FirstNavigationIntoPage/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_FirstNavigationIntoPage/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_IntraDocumentNavigation/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_IntraDocumentNavigation/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_Navigation/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerOriginTrialsBrowserTest.ForceEnabledOriginTrials_Navigation/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerUninstallBrowserTest.PRE_Uninstall/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerUninstallBrowserTest.PRE_Uninstall/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerUninstallBrowserTest.Uninstall/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerUninstallBrowserTest.Uninstall/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.Install/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.Install/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWork/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWork/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWorkFromAppProxy/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWorkFromAppProxy/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWorkWithIntent/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.LaunchMetricsWorkWithIntent/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.ToolbarVisibilityForSystemWebApp/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppManagerWebAppInfoBrowserTest.ToolbarVisibilityForSystemWebApp/_WebAppInfoInstall,N/A,,,,,,,,,,,,,,
+All/SystemWebAppNonClientFrameViewBrowserTest.HideNativeFileSystemAccessPageAction/_Default,N/A,,,,,,,,,,,,,,
+All/SystemWebAppNonClientFrameViewBrowserTest.HideWebAppMenuButton/_Default,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.DelayedManifestTriggersPipeline,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.DoesNotShowInIncognito,switch_incognito_profile,navigate_installable,assert_install_icon_not_shown,,,,,,,,,,,,
+AppBannerManagerBrowserTest.ListedRelatedChromeAppInstalled,N/A,Tests prefering a related installed application,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.ManifestChangeTriggersPipeline,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.MissingManifest,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.NoManifest,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.PreferRelatedAppUnknown,N/A,Tests prefering a related installed application,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.PreferRelatedChromeApp,N/A,Tests prefering a related installed application,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.RemovingManifestStopsPipeline,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerCancelled,N/A,Tests the oninstallprompt event cancellation,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerInIFrame,N/A,Tests that an page with an iframe to a webapp is not installable,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerInsufficientEngagement,N/A,Tests that the page doesn't show install prompt without sufficient engagement,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerNeedsEngagement,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerNotCreated,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerNoTypeInManifest,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerNoTypeInManifestCapsExtension,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerPromptWithGesture,navigate_installable,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerReprompt,N/A,,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerSvgIcon,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTest.WebAppBannerWebPIcon,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,,,,
+AppBannerManagerBrowserTestWithFailableInstallableManager.AppBannerManagerRetriesPipeline,N/A,,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest_DisplayOverride.InstallPromptAfterUserMenuInstall,navigate_installable(site_b),install_omnibox_or_menu,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest_DisplayOverride.PolicyAppInstalled_NoPrompt,add_policy_app_internal_tabbed_no_shortcut,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest_DisplayOverride.WebAppBannerResolvesUserChoice,navigate_installable,assert_install_icon_shown,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.DestroyWebContents,N/A,,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.InstallPromptAfterUserMenuInstall,N/A,,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.InstallPromptAfterUserOmniboxInstall,N/A,,,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.PolicyAppInstalled_NoPrompt,add_policy_app_internal_tabbed_no_shortcut,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.PolicyAppUninstalled_Prompt,add_policy_app_internal_tabbed_no_shortcut,remove_policy_app,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,,,,,
+AppBannerManagerDesktopBrowserTest.WebAppBannerResolvesUserChoice,navigate_installable,assert_install_icon_shown,,,,,,,,,,,,,
+AutomationManagerAuraBrowserTest.WebAppearsOnce,N/A,,,,,,,,,,,,,,
+AutoPictureInPictureWindowControllerBrowserTest.AutoEnterPictureInPictureIsNotTriggeredInRegularWebApp,N/A,,,,,,,,,,,,,,
+AutoPictureInPictureWindowControllerBrowserTest.AutoExitPictureInPictureIsTriggeredInRegularWebApp,N/A,,,,,,,,,,,,,,
+BrowserNonClientFrameViewBrowserTest.OpaqueFrameColorForTransparentWebAppThemeColor,install_internal_windowed,assert_window_color_correct,,,,,,,,,,,,,
+ContextMenuBrowserTest.OpenEntryInAppAbsentForURLsOutOfScopeOfWebApp,N/A,Tests context menu items for links - can open them in an 'app',,,,,,,,,,,,,
+ContextMenuBrowserTest.OpenInAppPresentForURLsInScopeOfNonWindowedWebApp,N/A,Tests context menu items for links - can open them in an 'app',,,,,,,,,,,,,
+ContextMenuBrowserTest.OpenInAppPresentForURLsInScopeOfWebApp,N/A,Tests context menu items for links - can open them in an 'app',,,,,,,,,,,,,
+ContextMenuBrowserTest.OpenLinkInWebApp,N/A,Tests context menu items for links - can open them in an 'app',,,,,,,,,,,,,
+CustomTabBarViewBrowserTest.IsUsedForDesktopPWA,N/A,,,,,,,,,,,,,,
+ExtensionCrxInstallerTest.InstallWebApp,N/A,Testing old bookmark app creation,,,,,,,,,,,,,
+ExtensionCrxInstallerTest.InstallWebAppSucceedsWithBlockPolicy,N/A,Testing old bookmark app creation,,,,,,,,,,,,,
+ExtensionCrxInstallerTest.InstallWebAppWithShortcuts,N/A,Testing old bookmark app creation,,,,,,,,,,,,,
+ExtensionPolicyTest.ExtensionAllowedTypes_WebApp,N/A,"Testing old bookmark app creation, webapps not blocked by extension policy",,,,,,,,,,,,,
+ExtensionPolicyTest.ExtensionInstallBlocklist_WebApp,N/A,"Testing old bookmark app creation, webapps not blocked by extension policy",,,,,,,,,,,,,
+ExtensionPolicyTest.ExtensionSettings_WebApp,N/A,"Testing old bookmark app creation, webapps not blocked by extension policy",,,,,,,,,,,,,
+ExternalWebAppManagerBrowserTest.LaunchQueryParamsBasic,N/A,add_default_app_windowed,launch_internal,,,,,,,,,,,,
+ExternalWebAppManagerBrowserTest.LaunchQueryParamsComplex,N/A,add_default_app_windowed,launch_internal,,,,,,,,,,,,
+ExternalWebAppManagerBrowserTest.UninstallAndReplace,N/A,add_default_extension,add_default_app_windowed_replace_extension,assert_extension_uninstalled,,,,,,,,,,,
+ExternalWebAppMigrationBrowserTest.MigratePreferences,N/A,Tests that things like page ordinal syncs across versions,,,,,,,,,,,,,
+ExternalWebAppMigrationBrowserTest.MigrateRevertMigrate,N/A,Migrate from default extension to default webapp back to default extension,,,,,,,,,,,,,
+ExternalWebAppMigrationBrowserTest.MigrateToPreinstalledWebApp,N/A,Migrate from default extension to default webapp ,,,,,,,,,,,,,
+ExternalWebAppMigrationBrowserTest.UserUninstalledExtensionApp,N/A,Don't migrate if user uninstalled,,,,,,,,,,,,,
+InstallableManagerAllowlistOriginBrowserTest.SecureOriginCheckRespectsUnsafeFlag,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest_DisplayOverride.CheckManifestOnly,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest_DisplayOverride.FallbackToDisplayBrowser,N/A,navigate_installable,assert_installable_internal,,,,,,,,,,,,
+InstallableManagerBrowserTest_DisplayOverride.ManifestDisplayOverrideReportsError,navigate_not_installable,assert_not_installable_internal,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckDataUrlIcon,N/A,navigate_installable,assert_installable_internal,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckInstallableParamsDefaultConstructor,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckLazyServiceWorkerNoFetchHandlerFails,navigate_not_installable,assert_not_installable_internal,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifest404,navigate_not_installable,assert_not_installable_internal,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifestAndIcon,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifestCorruptedIcon,navigate_not_installable,assert_not_installable_internal,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifestOnly,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifestWithIconThatIsTooSmall,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckManifestWithOnlyRelatedApplications,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckMaskableIcon,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckNavigationWithoutRunning,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckNestedCallsToGetData,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckNoManifest,navigate_not_installable,assert_not_installable_internal,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckPageWithManifestAndNoServiceWorker,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckPageWithNoServiceWorkerFetchHandler,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckSplashIcon,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.CheckWebappInIframe,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.DebugModeAccumulatesErrorsWithManifest,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.DebugModeBadFallbackMaskable,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.DebugModeWithNoManifest,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.GetAllInatallabilityErrorsNoErrors,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.GetAllInatallabilityErrorsWithNoManifest,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.GetAllInatallabilityErrorsWithPlayAppManifest,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.ManagerBeginsInEmptyState,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.ManagerInIncognito,switch_incognito_profile,navigate_installable,assert_not_installable_internal,,,,,,,,,,,,
+InstallableManagerBrowserTest.ManifestLinkChangeReportsError,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.ManifestUrlChangeFlushesState,N/A,,,,,,,,,,,,,,
+InstallableManagerBrowserTest.NarrowServiceWorker,N/A,,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.InstallableWebApp,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.InstallableWebAppInPlatformApp,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.InstallableWebAppWithStartUrl,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.NoGesture,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.NotInstallableWebApp,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+InstallReplacementWebAppApiTest.NotWebstore,N/A,API to install a replacement webapp for an extension,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckFindsDeletedDisplayOverride,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckFindsDisplayOverrideChange,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckFindsInvalidDisplayOverride,N/A ,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckFindsNewDisplayOverride,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckIgnoresDisplayOverrideChange,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest_DisplayOverride.CheckIgnoresDisplayOverrideInvalidChange,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckCancelledByAppUninstalled,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckCancelledByWebContentsDestroyed,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsDisplayBrowserChange,install_internal_windowed(site_a),manifest_update_display_browser,force_manifest_update_internal,assert_manifest_display_mode_standalone_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsDisplayChange,install_internal_windowed(site_a),manifest_update_display_minimal,force_manifest_update_internal,assert_manifest_display_mode_minimal_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsIconContentChange,install_internal_windowed(site_a),manifest_update_icons(site_a),force_manifest_update_internal,assert_icon_correct_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsIconUrlChange,install_internal_windowed(site_a),manifest_update_icons(site_a),force_manifest_update_internal,assert_icon_correct_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsScopeChange,install_internal_windowed(site_a_foo),manifest_update_scope_site_a_foo(site_a),force_manifest_update_internal,assert_scope_correct_internal_site_a_foo(site_a),,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckFindsThemeColorChange,install_internal_windowed,manifest_update_colors,force_manifest_update_internal,assert_theme_color_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresIconDownloadFail,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresInvalidManifest,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresNameChange,N/A,Webapps cannot update their name,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresNoManifestChange,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresNonLocalApps,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresPlaceholderApps,N/A,Don't manifest update placeholder apps from policy,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresShortNameChange,N/A,Webapps cannot update their name,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresStartUrlChange,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIgnoresWhitespaceDifferences,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckIsThrottled,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckKeepsSameName,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckOutOfScopeNavigation,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTest.CheckUpdatedPolicyAppsNotUninstallable,add_policy_app_internal_windowed_no_shortcut,manifest_update_colors,force_manifest_update_internal,assert_theme_color_internal,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsItemNameUpdated,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsItemUrlUpdated,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsShortcutIconContentChange,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsShortcutIconSizesUpdated,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsShortcutIconSrcUpdated,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckFindsShortcutsMenuUpdated,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerBrowserTestWithShortcutsMenu.CheckIgnoresShortNameAndDescriptionChange,N/A,Tests manifest updating for shortcut items in manifest,,,,,,,,,,,,,
+ManifestUpdateManagerSystemAppBrowserTest.CheckUpdateSkipped,N/A,,,,,,,,,,,,,,
+ManifestUpdateManagerWebAppsBrowserTest.CheckFindsAddedShareTarget,N/A,Manifest update test for share targets,,,,,,,,,,,,,
+ManifestUpdateManagerWebAppsBrowserTest.CheckFindsDeletedShareTarget,N/A,Manifest update test for share targets,,,,,,,,,,,,,
+ManifestUpdateManagerWebAppsBrowserTest.CheckFindsShareTargetChange,N/A,Manifest update test for share targets,,,,,,,,,,,,,
+ManifestUpdateManagerWebAppsBrowserTest.CheckFindsThemeColorChangeForShadowBookmarkApp,N/A,,,,,,,,,,,,,,
+ManifestWebAppTestWithFocusModeEnabled.MetricsTest,N/A,,,,,,,,,,,,,,
+ManifestWebAppTestWithFocusModeEnabled.OpenExampleSite,N/A,Metrics & UKM tests,,,,,,,,,,,,,
+PaymentManifestParserTest.InsecureWebAppManifestUrl,N/A,Metrics & UKM tests,,,,,,,,,,,,,
+PaymentManifestParserTest.InvalidWebAppManifestUrl,N/A,,,,,,,,,,,,,,
+PaymentManifestParserTest.TooManyWebAppManifestSections,N/A,,,,,,,,,,,,,,
+PaymentManifestParserTest.TooManyWebAppUrls,N/A,,,,,,,,,,,,,,
+PaymentManifestParserTest.WebAppManifest,N/A,,,,,,,,,,,,,,
+PWAConfirmationBubbleViewBrowserTest.AcceptBubbleInPWAWindowRunOnOsLoginChecked,N/A,,,,,,,,,,,,,,
+PWAConfirmationBubbleViewBrowserTest.AcceptBubbleInPWAWindowRunOnOsLoginUnchecked,N/A,,,,,,,,,,,,,,
+PWAConfirmationBubbleViewBrowserTest.CancelledDialogReportsMetrics,N/A,,,,,,,,,,,,,,
+PWAConfirmationBubbleViewBrowserTest.ShowBubbleInPWAWindow,navigate_installable,install_internal_windowed,launch_internal,,,,,,,,,,,,
+PWAMixedContentBrowserTest.IFrameMixedContentInPWA,N/A,mixed content shown in PWAs,,,,,,,,,,,,,
+PWAMixedContentBrowserTest.ShortcutMenuOptionsForNonInstallableSite,navigate_not_installable,assert_create_shortcut_shown,assert_install_icon_not_shown,,,,,,,,,,,,
+PWAMixedContentBrowserTestWithAutoupgradesDisabled.IFrameDynamicMixedContentInPWAOpenInChrome,N/A,,,,,,,,,,,,,,
+PWAMixedContentBrowserTestWithAutoupgradesDisabled.IFrameDynamicMixedContentInPWAReparentWebContentsIntoAppBrowser,N/A,,,,,,,,,,,,,,
+PWAMixedContentBrowserTestWithAutoupgradesDisabled.MixedContentInPWA,N/A,,,,,,,,,,,,,,
+PWAMixedContentBrowserTestWithAutoupgradesDisabled.MixedContentOpenInChrome,N/A,,,,,,,,,,,,,,
+PWAMixedContentBrowserTestWithAutoupgradesDisabled.MixedContentReparentWebContentsIntoAppBrowser,N/A,,,,,,,,,,,,,,
+SingleClientWebAppsSyncTest.DisablingSelectedTypeDisablesModelType,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.AppSortingFixCollisions,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.AppSortingSynced,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.DisplayMode,user_signin,navigate_installable,install_omnibox_or_menu,set_open_in_tab_internal,switch_profile_clients(user_a_client_2),assert_manifest_display_mode_browser_internal,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.DoubleInstallWithUninstall,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.NotSynced,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.NotSyncedThenSynced,N/A,"Installs default app that user installs elsewhere, check that it stays installed if default app is removed",,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.PolicyAppPersistsUninstalledOnSync,user_signin,add_policy_app_windowed_no_shortcut,switch_profile_clients(user_a_client_2),navigate_installable,install_omnibox_or_menu,uninstall_internal,switch_profile_clients(user_a_client_2),list_apps,assert_app_in_list_windowed,,,,,,
+TwoClientWebAppsBMOSyncTest.SyncDoubleInstallationDifferentNames,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsBMOSyncTest.UninstallSynced,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients(user_a_client_2),assert_app_not_locally_installed_internal,switch_profile_clients(user_a_client_1),uninstall_internal,switch_profile_clients(user_a_client_2),assert_app_not_installed_internal,navigate_installable,install_omnibox_or_menu,switch_profile_clients(user_a_client_1),uninstall_internal,switch_profile_clients(user_a_client_2),assert_app_not_installed_internal
+TwoClientWebAppsSyncTest.AppFieldsChangeDoesNotSync,N/A,Test that we only sync a few fields,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.Basic,user_signin,install_internal_windowed,switch_profile_clients(user_a_client_2),assert_app_not_locally_installed_internal,,,,,,,,,,,
+TwoClientWebAppsSyncTest.IsLocallyInstalled,user_signin,install_internal_windowed,switch_profile_clients(user_a_client_2),assert_app_not_locally_installed_internal,,,,,,,,,,,
+TwoClientWebAppsSyncTest.Minimal,user_signin,install_internal_windowed,switch_profile_clients(user_a_client_2),assert_app_not_locally_installed_internal,,,,,,,,,,,
+TwoClientWebAppsSyncTest.SyncFaviconOnly,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.SyncUsingIconUrlFallback,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.SyncUsingNameFallback,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.SyncUsingStartUrlFallback,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.SyncWithoutUsingNameFallback,N/A,,,,,,,,,,,,,,
+TwoClientWebAppsSyncTest.ThemeColor,N/A,,,,,,,,,,,,,,
+UnifiedAutoplayBrowserTest.MatchingWebAppScopeAllowsAutoplay_Origin,N/A,,,,,,,,,,,,,,
+UnifiedAutoplayBrowserTest.MatchingWebAppScopeAllowsAutoplay_Path,N/A,,,,,,,,,,,,,,
+UnifiedAutoplayBrowserTest.NotMatchingWebAppScopeDoesNotAllowAutoplay,N/A,,,,,,,,,,,,,,
+WebAppAudioFocusBrowserTest.AppHasDifferentAudioFocus,N/A,,,,,,,,,,,,,,
+WebAppAudioFocusBrowserTest.WebAppHasSameAudioFocus,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.AppServiceWorkerBadgeAffectsMultipleApps,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeCanBeClearedWithClearMethod,install_internal_windowed,launch_internal,set_app_badge,assert_app_badge_has_value,clear_app_badge,assert_app_badge_empty,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeCanBeClearedWithZero,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeCanBeSetAndClearedFromInScopeFrame,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeCanBeSetToAnInteger,install_internal_windowed,launch_internal,set_app_badge,assert_app_badge_has_value,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeCanBeSetWithoutAValue,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeSubFrameAppViaCall,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgeSubFrameAppViaNavigator,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.BadgingIncognitoWindowsDoesNotCrash,install_internal_windowed,switch_incognito_profile,navigate_browser_in_scope,set_app_badge,assert_app_badge_empty,,,,,,,,,,
+WebAppBadgingBrowserTest.CrossSiteFrameCannotChangeMainFrameBadge,install_internal_windowed(site_a),install_internal_windowed(site_b),launch_internal(site_a),set_app_badge(site_b),assert_app_badge_has_value(site_b),launch_internal(site_b),set_app_badge(site_a),assert_app_badge_has_value(site_a),,,,,,,
+WebAppBadgingBrowserTest.SubAppServiceWorkerBadgeAffectsSubApp,N/A,,,,,,,,,,,,,,
+WebAppBadgingBrowserTest.SubFrameBadgeAffectsSubApp,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest_DisplayOverride.DisplayOverride,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest_DisplayOverride.WithMinimalUiButtons,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest_DisplayOverride.WithoutMinimalUiButtons,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.AppInfoOpensPageInfo,install_internal_windowed,launch_internal,,,,,,,,,,,,,
+WebAppBrowserTest.AppLastLaunchTime,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.AutoGeneratedUserThemeCrash,install_internal_windowed,,,,,,,,,,,,,,
+WebAppBrowserTest.BackgroundColor,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.CanInstallOverTabPwa,navigate_installable,install_omnibox_or_menu,set_open_in_tab_internal,navigate_browser_in_scope,assert_install_icon_shown,assert_launch_icon_not_shown,assert_create_shortcut_shown,,,,,,,,
+WebAppBrowserTest.CannotInstallOverPolicyPwa,add_policy_app_internal_windowed_no_shortcut,navigate_browser_in_scope,assert_launch_icon_shown,assert_install_icon_not_shown,assert_create_shortcut_not_shown,,,,,,,,,,
+WebAppBrowserTest.CannotInstallOverWindowPwa,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,assert_launch_icon_shown,assert_install_icon_not_shown,assert_create_shortcut_shown,,,,,,,,,
+WebAppBrowserTest.CopyURL,install_internal_windowed,launch_internal,copy_pwa_url,,,,,,,,,,,,
+WebAppBrowserTest.DesktopPWAsOpenLinksInApp,install_internal_windowed,launch_internal,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,,
+WebAppBrowserTest.DesktopPWAsOpenLinksInNewTab,install_internal_windowed,launch_internal,navigate_link_target_blank,assert_tab_created,,,,,,,,,,,
+WebAppBrowserTest.EmptyTitlesDoNotDisplayUrl,install_internal_windowed,launch_internal,assert_correct_window_title,,,,,,,,,,,,
+WebAppBrowserTest.InScopeHttpUrlsDisplayAppTitle,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.InScopePWAPopupsHaveCorrectSize,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.InstallInstallableSite,navigate_installable,install_omnibox_or_menu,,,,,,,,,,,,,
+WebAppBrowserTest.InstallToShelfContainsAppName,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.LocationBarIsVisibleOffScopeOnSameOrigin,install_internal_windowed,launch_internal,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,,
+WebAppBrowserTest.MenuOptionsOutsideInstalledPwaScope,navigate_installable,install_omnibox_or_menu,navigate_browser_out_scope,assert_launch_icon_not_shown,assert_launch_icon_not_shown,assert_create_shortcut_shown,,,,,,,,,
+WebAppBrowserTest.NoTabSelectedMenuCrash,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.OffScopePWAPopupsHaveCorrectSize,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.OffScopeUrlsDisplayAppTitle,install_internal_windowed,launch_internal,navigate_pwa_out_scope,assert_correct_window_title,,,,,,,,,,,
+WebAppBrowserTest.OpenInChrome,install_internal_windowed,launch_internal,open_in_chrome,,,,,,,,,,,,
+WebAppBrowserTest.OverscrollEnabled,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.PermissionBubble,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.PopOutDisabledInIncognito,install_internal_windowed,switch_incognito_profile,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,,,,
+WebAppBrowserTest.PopupLocationBar,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.PWASizeIsCorrectlyRestored,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.ReparentLastBrowserTab,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.ReparentShortcutApp,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.ReparentWebAppForSecureActiveTab,install_internal_windowed,navigate_browser_in_scope,launch_from_omnibox_or_menu,,,,,,,,,,,,
+WebAppBrowserTest.ShortcutMenuOptionsForCrashedTab,navigate_crashed_url,assert_create_shortcut_not_shown,assert_install_icon_not_shown,,,,,,,,,,,,
+WebAppBrowserTest.ShortcutMenuOptionsForErrorPage,navigate_notfound_url,assert_create_shortcut_not_shown,assert_install_icon_not_shown,,,,,,,,,,,,
+WebAppBrowserTest.ShortcutMenuOptionsForInstallablePWA,navigate_installable,assert_create_shortcut_shown,assert_install_icon_shown,,,,,,,,,,,,
+WebAppBrowserTest.ShortcutMenuOptionsInIncognito,switch_incognito_profile,navigate_browser_in_scope,assert_create_shortcut_not_shown,,,,,,,,,,,,
+WebAppBrowserTest.SubframeRedirectsToWebApp,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.ThemeColor,install_internal_windowed,assert_theme_color,,,,,,,,,,,,,
+WebAppBrowserTest.UninstallMenuOption,install_internal_windowed,launch_internal,uninstall_from_menu,,,,,,,,,,,,
+WebAppBrowserTest.UpgradeWithoutCustomTabBar,install_internal_windowed,launch_internal,navigate_pwa_out_scope,assert_toolbar,,,,,,,,,,,
+WebAppBrowserTest.WithMinimalUiButtons,N/A,,,,,,,,,,,,,,
+WebAppBrowserTest.WithoutMinimalUiButtons,N/A,,,,,,,,,,,,,,
+WebAppConfirmViewBrowserTest.ShowWebAppInstallDialog,navigate_installable,assert_install_icon_shown,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.AppInTab,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.AppInWindow,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.AppWithoutScope,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.CommandLineTab,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.CommandLineWindow,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.DefaultApp,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.ManyUserApps,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.NavigateAwayFromAppTab,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.RecordedForNonApps,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppEngagementBrowserTest.TwoApps,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppFileHandlerRegistrationLinuxBrowserTest.RegisterMimeTypesOnLinuxCallbackCalledSuccessfully,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingBrowserTest.LaunchConsumerIsNotTriggeredWithNoFiles,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingBrowserTest.PWAsCanReceiveFileLaunchParams,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingBrowserTest.PWAsCanReceiveFileLaunchParamsInTab,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingBrowserTest.PWAsDispatchOnCorrectFileHandlingURL,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.DisableForceEnabledFileHandlingOriginTrial,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.ExpiredTrialHandlersAreCleanedUpAtLaunch,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.FileHandlingIsNotAvailableUntilOriginTrialIsChecked,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.FileHandlingOriginTrialIsCheckedAtInstallation,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.ForceEnabledFileHandling_IgnoreExpiryTimeInflightIPC,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.ForceEnabledFileHandling_IgnoreExpiryTimeUpdate,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.PRE_ExpiredTrialHandlersAreCleanedUpAtLaunch,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.PRE_ValidFileHandlerAreNotCleanedUpAtLaunch,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.ValidFileHandlerAreNotCleanedUpAtLaunch,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialBrowserTest.WhenOriginTrialHasExpiredFileHandlersAreNotAvailable,N/A,,,,,,,,,,,,,,
+WebAppFileHandlingOriginTrialTest.LaunchParamsArePassedCorrectly,N/A,,,,,,,,,,,,,,
+WebAppFrameToolbarBrowserTest.SpaceConstrained,N/A,,,,,,,,,,,,,,
+WebAppFrameToolbarBrowserTest.ThemeChange,N/A,,,,,,,,,,,,,,
+WebAppFrameToolbarBrowserTest.TitleHover,N/A,,,,,,,,,,,,,,
+WebAppIconManagerBrowserTest.SingleIcon,install_internal_windowed,assert_icon_correct_internal,,,,,,,,,,,,,
+WebAppInstallForceListPolicyTest.StartUpInstallation,add_policy_app_internal_windowed_no_shortcut,assert_manifest_display_mode_standalone_internal,,,,,,,,,,,,,
+WebAppLinkCapturingBrowserTest.AboutBlankNavigationReparented,N/A,Testing tabbed display mode link capturing,,,,,,,,,,,,,
+WebAppLinkCapturingBrowserTest.InScopeNavigationsCaptured,N/A,Testing tabbed display mode link capturing,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.InstalledWebApp_RecordsTimeAndSessions,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.InstalledWebApp_RecordsZeroTimeIfOverLimit,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.InstalledWebAppInTab_RecordsDailyInteraction,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.InstalledWebAppInWindow_RecordsDailyInteractionWithSessionDurations,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.MultipleWebAppInstances_StillRecordsTime,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.NavigationsWithinInstalledWebApp_RecordsOneSession,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.NonInstalledWebApp_RecordsDailyInteraction,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.NonWebApp_RecordsNothing,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMetricsBrowserTest.Suspend_FlushesSessionTimes,N/A,Tests metrics for UKM and UMA.,,,,,,,,,,,,,
+WebAppMigrationManagerBrowserTest.DatabaseMigration_HiddenFromUser,N/A,,,,,,,,,,,,,,
+WebAppMigrationManagerBrowserTest.DatabaseMigration_SimpleManifest,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,assert_manifest_display_mode_standalone_internal,,,,,,,,,,,
+WebAppMigrationManagerBrowserTest.InstallShadowBookmarkApp,N/A,,,,,,,,,,,,,,
+WebAppMigrationManagerBrowserTest.UninstallShadowBookmarkApp,N/A,,,,,,,,,,,,,,
+WebAppMigrationManagerBrowserTestWithShortcutsMenu.DatabaseMigration_ManifestWithShortcutsMenu,N/A,,,,,,,,,,,,,,
+WebAppMigrationUserDisplayModeCleanUpBrowserTest.CleanUp,N/A,,,,,,,,,,,,,,
+WebAppMinimalUITest.MinimalUi,install_internal_windowed(site_b),launch_internal(site_b),assert_window_created,assert_window_display_minimal,,,,,,,,,,,
+WebAppMinimalUITest.Standalone,install_internal_windowed(site_a),launch_internal(site_b),assert_window_created,assert_window_display_standalone,,,,,,,,,,,
+WebAppNavigateBrowserTest.AppInstalled_OpenAppWindowIfPossible_False,N/A,Tabbed mode stuff.,,,,,,,,,,,,,
+WebAppNavigateBrowserTest.AppInstalled_OpenAppWindowIfPossible_True,N/A,Tabbed mode stuff.,,,,,,,,,,,,,
+WebAppNavigateBrowserTest.NewPopup,N/A,Tabbed mode stuff.,,,,,,,,,,,,,
+WebAppNavigateBrowserTest.NoAppInstalled_OpenAppWindowIfPossible,N/A,Tabbed mode stuff.,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.DarkThemeColor,N/A,,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.LightThemeColor,N/A,,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.MediumThemeColor,N/A,,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.NoThemeColor,N/A,,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.StaticTitleBarHeight,N/A,,,,,,,,,,,,,,
+WebAppOpaqueBrowserFrameViewTest.SystemThemeColor,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPicture,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureNotTriggeredIfDocumentNotInWebAppScope,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureNotTriggeredIfVideoNotPlaying,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureNotTriggeredOnPageShownIfNoAttribute,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureNotTriggeredOnPageShownIfNotEnteredAutoPictureInPicture,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureTriggeredOnPageHiddenIfVideoPausedWhenHidden,N/A,,,,,,,,,,,,,,
+WebAppPictureInPictureWindowControllerBrowserTest.AutoPictureInPictureWhenPictureInPictureWindowAlreadyVisible,N/A,,,,,,,,,,,,,,
+WebAppProfileDeletionBrowserTest.AppRegistrarNotifiesProfileDeletion,N/A,,,,,,,,,,,,,,
+WebAppTabRestoreBrowserTest.ReopenedPWASizeIsCorrectlyRestored,N/A,,,,,,,,,,,,,,
+WebAppTabRestoreBrowserTest.RestoreAppWindow,N/A,,,,,,,,,,,,,,
+WebAppTabStripBrowserTest.CustomTabBarUpdateOnTabSwitch,N/A,Tabbed mode stuff.,,,,,,,,,,,,,
+WebAppUiManagerImplBrowserTest.GetNumWindowsForApp_AppWindowsAdded,N/A,,,,,,,,,,,,,,
+WebAppUiManagerImplBrowserTest.GetNumWindowsForApp_AppWindowsRemoved,N/A,,,,,,,,,,,,,,
+WebAppUiManagerImplBrowserTest.NotifyOnAllAppWindowsClosed_MultipleOpenedWindows,N/A,,,,,,,,,,,,,,
+WebAppUiManagerImplBrowserTest.NotifyOnAllAppWindowsClosed_NoOpenedWindows,N/A,,,,,,,,,,,,,,
+WebAppUninstallBrowserTest.RestoreAppWindowForUninstalledApp,N/A,,,,,,,,,,,,,,
+WebAppUninstallBrowserTest.UninstallPwaWithWindowMovedToTab,install_internal_windowed,launch_internal,open_in_chrome,uninstall_internal,,,,,,,,,,,
+WebAppUninstallBrowserTest.UninstallPwaWithWindowOpened,install_internal_windowed,launch_internal,uninstall_internal,assert_window_closed,,,,,,,,,,,
+WebAppUninstallDialogViewBrowserTest.TestDialogUserFlow_Accept,N/A,,,,,,,,,,,,,,
+WebAppUninstallDialogViewBrowserTest.TestDialogUserFlow_Cancel,N/A,,,,,,,,,,,,,,
+WebAppUninstallDialogViewBrowserTest.TrackParentWindowDestruction,N/A,,,,,,,,,,,,,,
+WebAppUninstallDialogViewBrowserTest.TrackParentWindowDestructionAfterViewCreation,N/A,,,,,,,,,,,,,,
+WebAppUninstallDialogViewInteractiveBrowserTest.InvokeUi_ManualUninstall,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.302FoundRedirect,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.Hung,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.Loaded,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.LoadedWithParamAndRefChangeIgnored,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.LoadedWithParamChangeIgnored,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.LoadedWithPathChangeIgnored,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.MultipleLoadUrlCalls,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.NetworkError,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.PrepareForLoad_AfterNavigationComplete,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.PrepareForLoad_BeforeNavigationComplete,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.PrepareForLoad_RecordResultMetric,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.RedirectWithParamChange,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.RedirectWithPathChange,N/A,,,,,,,,,,,,,,
+WebAppUrlLoaderTest.WebContentsDestroyed,N/A,,,,,,,,,,,,,,
+WebrtcLoggingPrivateApiStartEventLoggingTestFeatureAndPolicyEnabled.StartEventLoggingForTooHighWebAppIdFails,N/A,,,,,,,,,,,,,,
+WebrtcLoggingPrivateApiStartEventLoggingTestFeatureAndPolicyEnabled.StartEventLoggingForTooLowWebAppIdFails,N/A,,,,,,,,,,,,,,
\ No newline at end of file
diff --git a/chrome/test/webapps/data/coverage_required.csv b/chrome/test/webapps/data/coverage_required.csv
new file mode 100644
index 0000000..01c59f6
--- /dev/null
+++ b/chrome/test/webapps/data/coverage_required.csv
@@ -0,0 +1,208 @@
+# Affected-by Edge Action 1,Affected-by Edge Action 2,Test,,,,,,,,,,,
+add_policy_app_tabbed_no_shortcut,assert_app_in_list_not_windowed,user_signin,add_policy_app_tabbed_no_shortcut,list_apps,assert_app_in_list_not_windowed,,,,,,,,
+add_policy_app_tabbed_no_shortcut,assert_create_shortcut_shown,user_signin,add_policy_app_tabbed_no_shortcut,navigate_browser_in_scope,assert_create_shortcut_shown,,,,,,,,
+add_policy_app_tabbed_no_shortcut,assert_install_icon_shown,user_signin,add_policy_app_tabbed_no_shortcut,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,,
+add_policy_app_tabbed_no_shortcut,assert_launch_icon_not_shown,user_signin,add_policy_app_tabbed_no_shortcut,navigate_browser_in_scope,assert_launch_icon_not_shown,,,,,,,,
+add_policy_app_tabbed_no_shortcut,assert_platform_shortcut_not_exists,user_signin,add_policy_app_tabbed_no_shortcut,assert_platform_shortcut_not_exists,,,,,,,,,
+add_policy_app_tabbed_no_shortcut,chrome_update,user_signin,add_policy_app_tabbed_no_shortcut,chrome_update,assert_platform_shortcut_not_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_tabbed_no_shortcut,chrome_update_with_bmo_migration,user_signin,add_policy_app_tabbed_no_shortcut,chrome_update_with_bmo_migration,assert_platform_shortcut_not_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_tabbed_no_shortcut,delete_profile,user_signin,add_policy_app_tabbed_no_shortcut,delete_profile,list_apps,assert_app_not_in_list,,,,,,,
+add_policy_app_tabbed_no_shortcut,remove_policy_app,user_signin,add_policy_app_tabbed_no_shortcut,remove_policy_app,list_apps,assert_app_not_in_list,,,,,,,
+add_policy_app_tabbed_shortcut,assert_app_in_list_not_windowed,user_signin,add_policy_app_tabbed_shortcut,list_apps,assert_app_in_list_not_windowed,,,,,,,,
+add_policy_app_tabbed_shortcut,assert_create_shortcut_shown,user_signin,add_policy_app_tabbed_shortcut,navigate_browser_in_scope,assert_create_shortcut_shown,,,,,,,,
+add_policy_app_tabbed_shortcut,assert_install_icon_shown,user_signin,add_policy_app_tabbed_shortcut,navigate_browser_in_scope,assert_launch_icon_shown,,,,,,,,
+add_policy_app_tabbed_shortcut,assert_launch_icon_not_shown,user_signin,add_policy_app_tabbed_shortcut,navigate_browser_in_scope,assert_launch_icon_not_shown,,,,,,,,
+add_policy_app_tabbed_shortcut,assert_platform_shortcut_exists,user_signin,add_policy_app_tabbed_shortcut,assert_platform_shortcut_exists,,,,,,,,,
+add_policy_app_tabbed_shortcut,chrome_update,user_signin,add_policy_app_tabbed_shortcut,chrome_update,assert_platform_shortcut_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_tabbed_shortcut,chrome_update_with_bmo_migration,user_signin,add_policy_app_tabbed_shortcut,chrome_update_with_bmo_migration,assert_platform_shortcut_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_tabbed_shortcut,delete_profile,user_signin,add_policy_app_tabbed_shortcut,delete_profile,assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list,,,,,,
+add_policy_app_tabbed_shortcut,remove_policy_app,user_signin,add_policy_app_tabbed_shortcut,remove_policy_app,assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list,,,,,,
+add_policy_app_windowed_no_shortcut,assert_app_in_list_windowed,user_signin,add_policy_app_windowed_no_shortcut,list_apps,assert_app_in_list_windowed,,,,,,,,
+add_policy_app_windowed_no_shortcut,assert_create_shortcut_not_shown,user_signin,add_policy_app_windowed_no_shortcut,navigate_browser_in_scope,assert_create_shortcut_not_shown,,,,,,,,
+add_policy_app_windowed_no_shortcut,assert_install_icon_not_shown,user_signin,add_policy_app_windowed_no_shortcut,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,
+add_policy_app_windowed_no_shortcut,assert_launch_icon_shown,user_signin,add_policy_app_windowed_no_shortcut,navigate_browser_in_scope,assert_launch_icon_shown,,,,,,,,
+add_policy_app_windowed_no_shortcut,assert_platform_shortcut_not_exists,user_signin,add_policy_app_windowed_no_shortcut,assert_platform_shortcut_not_exists,,,,,,,,,
+add_policy_app_windowed_no_shortcut,chrome_update,user_signin,add_policy_app_windowed_no_shortcut,chrome_update,assert_platform_shortcut_not_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_windowed_no_shortcut,chrome_update_with_bmo_migration,user_signin,add_policy_app_windowed_no_shortcut,chrome_update_with_bmo_migration,assert_platform_shortcut_not_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+add_policy_app_windowed_no_shortcut,delete_profile,user_signin,add_policy_app_windowed_no_shortcut,delete_profile,list_apps,assert_app_not_in_list,,,,,,,
+add_policy_app_windowed_no_shortcut,remove_policy_app,user_signin,add_policy_app_windowed_no_shortcut,remove_policy_app,list_apps,assert_app_not_in_list,,,,,,,
+add_policy_app_windowed_shortcut,assert_app_in_list_windowed,user_signin,add_policy_app_windowed_shortcut,list_apps,assert_app_in_list_windowed,,,,,,,,
+add_policy_app_windowed_shortcut,assert_create_shortcut_not_shown,user_signin,add_policy_app_windowed_shortcut,navigate_browser_in_scope,assert_create_shortcut_not_shown,,,,,,,,
+add_policy_app_windowed_shortcut,assert_install_icon_not_shown,user_signin,add_policy_app_windowed_shortcut,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,
+add_policy_app_windowed_shortcut,assert_launch_icon_shown,user_signin,add_policy_app_windowed_shortcut,navigate_browser_in_scope,assert_launch_icon_shown,,,,,,,,
+add_policy_app_windowed_shortcut,assert_platform_shortcut_exists,user_signin,add_policy_app_windowed_shortcut,assert_platform_shortcut_exists,,,,,,,,,
+add_policy_app_windowed_shortcut,chrome_update,user_signin,add_policy_app_windowed_shortcut,chrome_update,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,,,,,,
+add_policy_app_windowed_shortcut,chrome_update_with_bmo_migration,user_signin,add_policy_app_windowed_shortcut,chrome_update_with_bmo_migration,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,,,,,,
+add_policy_app_windowed_shortcut,delete_profile,user_signin,add_policy_app_windowed_shortcut,delete_profile,assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list,,,,,,
+add_policy_app_windowed_shortcut,remove_policy_app,user_signin,add_policy_app_windowed_shortcut,remove_policy_app,assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list,,,,,,
+chrome_update,launch_from_omnibox_or_menu,navigate_not_installable,install_create_shortcut_tabbed,chrome_update,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_tab_created,,,,,,
+chrome_update,launch_from_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,chrome_update,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,,,,,
+chrome_update,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_tabbed,chrome_update,launch_from_platform_shortcut,assert_tab_created,,,,,,,
+chrome_update,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,chrome_update,launch_from_platform_shortcut,assert_window_created,,,,,,,
+chrome_update,list_apps,navigate_not_installable,install_create_shortcut_tabbed,chrome_update,list_apps,assert_app_in_list_not_windowed,,,,,,,
+chrome_update,list_apps,navigate_installable,install_omnibox_or_menu,chrome_update,list_apps,assert_app_in_list_windowed,,,,,,,
+chrome_update,list_apps,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,chrome_update,list_apps,assert_app_in_list_not_locally_installed,launch_from_list,assert_tab_created,,,
+chrome_update,list_apps,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,chrome_update,list_apps,assert_app_in_list_not_locally_installed,,,,,
+chrome_update,navigate_browser_in_scope,navigate_installable,install_create_shortcut_tabbed,chrome_update,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,
+chrome_update,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,chrome_update,navigate_browser_in_scope,assert_install_icon_not_shown,assert_launch_icon_shown,,,,,,
+chrome_update,uninstall_from_menu,navigate_installable,install_omnibox_or_menu,chrome_update,launch_from_platform_shortcut,uninstall_from_menu,assert_window_closed,list_apps,assert_app_not_in_list,,,,
+chrome_update_with_bmo_migration,launch_from_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,,,,,
+chrome_update_with_bmo_migration,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_tabbed,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_tab_created,,,,,,,
+chrome_update_with_bmo_migration,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_window_created,,,,,,,
+chrome_update_with_bmo_migration,list_apps,navigate_not_installable,install_create_shortcut_tabbed,chrome_update_with_bmo_migration,assert_platform_shortcut_exists,list_apps,assert_app_in_list_not_windowed,,,,,,
+chrome_update_with_bmo_migration,list_apps,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,,,,,,
+chrome_update_with_bmo_migration,list_apps,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_locally_installed,launch_from_list,assert_tab_created,,,
+chrome_update_with_bmo_migration,list_apps,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_locally_installed,,,,,
+chrome_update_with_bmo_migration,navigate_browser_in_scope,navigate_installable,install_create_shortcut_tabbed,chrome_update_with_bmo_migration,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,
+chrome_update_with_bmo_migration,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,
+chrome_update_with_bmo_migration,uninstall_from_menu,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,launch_from_platform_shortcut,uninstall_from_menu,assert_window_closed,list_apps,assert_app_not_in_list,,,,
+clear_app_badge,assert_app_badge_empty,navigate_installable,install_omnibox_or_menu,set_app_badge,clear_app_badge,assert_app_badge_empty,,,,,,,
+create_shortcuts,chrome_update,navigate_not_installable,install_create_shortcut_tabbed,delete_platform_shortcut,create_shortcuts,chrome_update,launch_from_platform_shortcut,assert_tab_created,,,,,
+create_shortcuts,chrome_update,navigate_installable,install_omnibox_or_menu,delete_platform_shortcut,create_shortcuts,chrome_update,launch_from_platform_shortcut,assert_window_created,,,,,
+create_shortcuts,chrome_update_with_bmo_migration,navigate_not_installable,install_create_shortcut_tabbed,delete_platform_shortcut,create_shortcuts,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_tab_created,,,,,
+create_shortcuts,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,delete_platform_shortcut,create_shortcuts,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_window_created,,,,,
+create_shortcuts,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_tabbed,delete_platform_shortcut,create_shortcuts,launch_from_platform_shortcut,assert_tab_created,,,,,,
+create_shortcuts,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,delete_platform_shortcut,create_shortcuts,launch_from_platform_shortcut,assert_window_created,,,,,,
+delete_profile,assert_platform_shortcut_not_exists,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,assert_platform_shortcut_not_exists,,,,,,,
+delete_profile,install_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,delete_profile,navigate_installable,install_omnibox_or_menu,assert_window_created,,,,,,
+delete_profile,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,delete_profile,navigate_installable,install_omnibox_or_menu,close_pwa,launch_from_platform_shortcut,assert_window_created,,,,
+delete_profile,list_apps,navigate_installable,install_omnibox_or_menu,delete_profile,list_apps,assert_app_list_empty,,,,,,,
+delete_profile,list_apps,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,list_apps,assert_app_not_in_list,,,,,,
+install_create_shortcut_tabbed,add_policy_app_windowed_no_shortcut,navigate_installable,install_create_shortcut_tabbed,user_signin,add_policy_app_windowed_no_shortcut,assert_platform_shortcut_exists,list_apps,assert_app_in_list_not_windowed,,,,,
+install_create_shortcut_tabbed,chrome_update,navigate_not_installable,install_create_shortcut_tabbed,chrome_update,list_apps,assert_app_in_list_not_windowed,,,,,,,
+install_create_shortcut_tabbed,chrome_update_with_bmo_migration,navigate_not_installable,install_create_shortcut_tabbed,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_windowed,,,,,,,
+install_create_shortcut_tabbed,launch_from_list,navigate_not_installable,install_create_shortcut_tabbed,list_apps,launch_from_list,assert_tab_created,,,,,,,
+install_create_shortcut_tabbed,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_tabbed,launch_from_platform_shortcut,assert_tab_created,,,,,,,,
+install_create_shortcut_tabbed,list_apps,navigate_not_installable,install_create_shortcut_tabbed,list_apps,assert_app_in_list_not_windowed,,,,,,,,
+install_create_shortcut_tabbed,navigate_browser_in_scope,navigate_not_installable,install_create_shortcut_tabbed,navigate_browser_in_scope,assert_launch_icon_not_shown,,,,,,,,
+install_create_shortcut_tabbed,remove_policy_app,navigate_installable,install_create_shortcut_tabbed,user_signin,add_policy_app_tabbed_no_shortcut,remove_policy_app,assert_platform_shortcut_exists,list_apps,assert_app_in_list_not_windowed,,,,
+install_create_shortcut_windowed,chrome_update,navigate_not_installable,install_create_shortcut_windowed,chrome_update,list_apps,assert_app_in_list_windowed,,,,,,,
+install_create_shortcut_windowed,chrome_update_with_bmo_migration,navigate_not_installable,install_create_shortcut_windowed,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_windowed,,,,,,,
+install_create_shortcut_windowed,launch_from_list,navigate_not_installable,install_create_shortcut_windowed,list_apps,launch_from_list,assert_tab_created,,,,,,,
+install_create_shortcut_windowed,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_windowed,launch_from_platform_shortcut,assert_tab_created,,,,,,,,
+install_create_shortcut_windowed,list_apps,navigate_not_installable,install_create_shortcut_windowed,list_apps,assert_app_in_list_windowed,,,,,,,,
+install_create_shortcut_windowed,navigate_browser_in_scope,launch_from_omnibox_or_menu,navigate_installable,install_create_shortcut_windowed,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,,,,,
+install_locally,assert_platform_shortcut_created,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,assert_platform_shortcut_created,,,,,
+install_locally,assert_platform_shortcut_created,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,assert_platform_shortcut_created,,,,,
+install_locally,chrome_update,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,chrome_update,launch_from_platform_shortcut,assert_window_created,,,
+install_locally,chrome_update,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,chrome_update,launch_from_platform_shortcut,assert_tab_created,,,
+install_locally,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_window_created,,,
+install_locally,chrome_update_with_bmo_migration,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,chrome_update_with_bmo_migration,launch_from_platform_shortcut,assert_tab_created,,,
+install_locally,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,launch_from_platform_shortcut,assert_window_created,,,,
+install_locally,launch_from_platform_shortcut,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,launch_from_platform_shortcut,assert_tab_created,,,,
+install_locally,list_apps,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,list_apps,assert_app_in_list_windowed,,,,
+install_locally,list_apps,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,list_apps,assert_app_in_list_not_windowed,,,,
+install_locally,navigate_browser_in_scope,launch_from_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,
+install_omnibox_or_menu,add_policy_app_tabbed_no_shortcut,navigate_installable,install_omnibox_or_menu,user_signin,add_policy_app_tabbed_no_shortcut,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,,,,,
+install_omnibox_or_menu,assert_platform_shortcut_created,navigate_installable,install_omnibox_or_menu,assert_platform_shortcut_created,,,,,,,,,
+install_omnibox_or_menu,assert_platform_shortcut_right_click_menu_has_actions,navigate_installable(site_b),install_omnibox_or_menu,assert_platform_shortcut_right_click_menu_has_actions,,,,,,,,,
+install_omnibox_or_menu,chrome_update,navigate_installable,install_omnibox_or_menu,chrome_update,list_apps,assert_app_in_list_windowed,,,,,,,
+install_omnibox_or_menu,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_windowed,,,,,,,
+install_omnibox_or_menu,launch_from_list,navigate_installable,install_omnibox_or_menu,list_apps,launch_from_list,assert_window_created,,,,,,,
+install_omnibox_or_menu,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,assert_window_created,,,,,,,,
+install_omnibox_or_menu,list_apps,navigate_installable,install_omnibox_or_menu,list_apps,assert_app_in_list_windowed,,,,,,,,
+install_omnibox_or_menu,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,assert_install_icon_not_shown,assert_launch_icon_shown,,,,,,,
+install_omnibox_or_menu,navigate_browser_in_scope(site_a_foo),navigate_installable(site_a),install_omnibox_or_menu,navigate_browser_in_scope(site_a_foo),assert_install_icon_not_shown,assert_launch_icon_shown,,,,,,,
+install_omnibox_or_menu,navigate_browser_out_scope,navigate_installable,install_omnibox_or_menu,navigate_browser_out_scope(site_b),assert_launch_icon_not_shown,,,,,,,,
+install_omnibox_or_menu,remove_policy_app,navigate_installable,install_omnibox_or_menu,user_signin,add_policy_app_windowed_shortcut,remove_policy_app,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,,,,
+launch_from_list,navigate_pwa_out_scope,navigate_installable,install_omnibox_or_menu,launch_from_list,navigate_pwa_out_scope,assert_toolbar,,,,,,,
+launch_from_list(site_b),use_pwa_window_controls,navigate_installable(site_b),install_omnibox_or_menu,list_apps,launch_from_list(site_b),use_pwa_window_controls,,,,,,,
+launch_from_omnibox_or_menu,assert_window_display_minimal,navigate_installable(site_b),install_omnibox_or_menu,launch_from_omnibox_or_menu,assert_window_display_minimal,,,,,,,,
+launch_from_omnibox_or_menu,assert_window_display_standalone,navigate_installable(site_a),install_omnibox_or_menu,launch_from_omnibox_or_menu,assert_window_display_standalone,,,,,,,,
+launch_from_omnibox_or_menu,close_pwa,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,launch_from_omnibox_or_menu,close_pwa,,,,,,,
+launch_from_omnibox_or_menu,navigate_pwa_out_scope,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,launch_from_omnibox_or_menu,navigate_pwa_out_scope,assert_toolbar,,,,,,
+launch_from_omnibox_or_menu,use_pwa_window_controls,navigate_installable(site_b),install_omnibox_or_menu,navigate_browser_in_scope(site_b),launch_from_omnibox_or_menu,use_pwa_window_controls,,,,,,,
+launch_from_platform_shortcut,assert_correct_window_title,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,assert_correct_window_title,,,,,,,,
+launch_from_platform_shortcut,assert_window_display_standalone,navigate_installable(site_a),install_omnibox_or_menu,launch_from_platform_shortcut,assert_window_display_standalone,,,,,,,,
+launch_from_platform_shortcut,close_pwa,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,close_pwa,,,,,,,,
+launch_from_platform_shortcut,navigate_pwa_out_scope,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,navigate_pwa_out_scope,assert_toolbar,,,,,,,
+launch_from_platform_shortcut,navigate_pwa_out_scope,assert_correct_window_title,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,navigate_pwa_out_scope,assert_correct_window_title,,,,,,
+launch_from_platform_shortcut(site_b),assert_window_display_minimal,navigate_installable(site_b),install_omnibox_or_menu,launch_from_platform_shortcut(site_b),assert_window_display_minimal,,,,,,,,
+launch_from_platform_shortcut(site_b),use_pwa_window_controls,navigate_installable(site_b),install_omnibox_or_menu,launch_from_platform_shortcut(site_b),use_pwa_window_controls,,,,,,,,
+manifest_update_colors,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,manifest_update_colors,close_pwa,launch_from_platform_shortcut,close_pwa,launch_from_platform_shortcut,assert_window_color_correct,,,,
+manifest_update_display_browser,launch_from_platform_shortcut,navigate_installable(site_a),install_omnibox_or_menu,manifest_update_display_browser,close_pwa,launch_from_platform_shortcut,close_pwa,launch_from_platform_shortcut,assert_window_display_standalone,,,,
+manifest_update_display_minimal,launch_from_platform_shortcut,navigate_installable(site_a),install_omnibox_or_menu,manifest_update_display_minimal,close_pwa,launch_from_platform_shortcut,close_pwa,launch_from_platform_shortcut,assert_window_display_minimal,,,,
+manifest_update_icons,assert_platform_shortcut_icon_correct,navigate_installable,install_omnibox_or_menu,manifest_update_icons,close_pwa,launch_from_platform_shortcut,close_pwa,launch_from_platform_shortcut,assert_platform_shortcut_icon_correct,,,,
+manifest_update_icons,list_apps,navigate_installable,install_omnibox_or_menu,manifest_update_icons,close_pwa,launch_from_platform_shortcut,close_pwa,list_apps,assert_app_in_list_icon_correct,,,,
+manifest_update_scope_site_a_foo(site_a),navigate_pwa_in_scope(site_a_bar),navigate_installable(site_a_foo),install_omnibox_or_menu,manifest_update_scope_site_a_foo(site_a),close_pwa,launch_from_platform_shortcut(site_a_foo),close_pwa,navigate_pwa_in_scope(site_a_bar),assert_no_toolbar,,,,
+manifest_update_scope_site_a(site_a_foo),navigate_browser_in_scope(site_a_foo),navigate_installable(site_a),install_omnibox_or_menu,manifest_update_scope_site_a(site_a_foo),close_pwa,launch_from_platform_shortcut,close_pwa,navigate_browser_in_scope(site_a_foo),assert_launch_icon_shown,,,,
+manifest_update_scope_site_a(site_a_foo),navigate_browser_out_scope,navigate_installable(site_a),install_omnibox_or_menu,manifest_update_scope_site_a(site_a_foo),close_pwa,launch_from_platform_shortcut,close_pwa,navigate_browser_out_scope,assert_launch_icon_not_shown,navigate_browser_in_scope,assert_launch_icon_shown,,
+manifest_update_scope_site_a(site_a_foo),navigate_installable(site_a_bar),navigate_installable(site_a),install_omnibox_or_menu,manifest_update_scope_site_a(site_a_foo),close_pwa,launch_from_platform_shortcut,close_pwa,navigate_installable(site_a_bar),assert_install_icon_shown,,,,
+manifest_update_scope_site_a(site_a_foo),navigate_pwa_in_scope(site_a_foo),navigate_installable(site_a),install_omnibox_or_menu,manifest_update_scope_site_a(site_a_foo),close_pwa,launch_from_platform_shortcut,close_pwa,navigate_pwa_in_scope(site_a_foo),assert_no_toolbar,,,,
+manifest_update_scope_site_a(site_a_foo),navigate_pwa_out_scope(site_a_bar),navigate_installable(site_a),install_omnibox_or_menu,manifest_update_scope_site_a(site_a_foo),close_pwa,launch_from_platform_shortcut,close_pwa,navigate_pwa_out_scope(site_a_bar),assert_toolbar,,,,
+navigate_browser_in_scope,assert_install_icon_not_shown,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,assert_install_icon_not_shown,,,,,,,,
+navigate_browser_in_scope,assert_launch_icon_shown,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,assert_launch_icon_shown,,,,,,,,
+navigate_browser_in_scope,launch_from_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,,,,,,
+navigate_browser_out_scope,assert_install_icon_shown,navigate_installable,install_omnibox_or_menu,navigate_browser_out_scope(site_b),assert_install_icon_shown,,,,,,,,
+navigate_browser_out_scope,assert_launch_icon_not_shown,navigate_installable,install_omnibox_or_menu,navigate_browser_out_scope(site_b),assert_launch_icon_not_shown,,,,,,,,
+navigate_crashed_url,assert_create_shortcut_not_shown,navigate_crashed_url,assert_create_shortcut_not_shown,,,,,,,,,,
+navigate_installable,assert_create_shortcut_shown,navigate_installable,assert_create_shortcut_shown,,,,,,,,,,
+navigate_installable,install_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,assert_window_created,list_apps,assert_app_in_list_windowed,,,,,,,
+navigate_not_installable,assert_create_shortcut_shown,navigate_not_installable,assert_create_shortcut_shown,,,,,,,,,,
+navigate_not_installable,assert_install_icon_not_shown,navigate_not_installable,assert_install_icon_not_shown,,,,,,,,,,
+navigate_notfound_url,assert_create_shortcut_not_shown,navigate_notfound_url,assert_create_shortcut_not_shown,,,,,,,,,,
+navigate_notfound_url,assert_install_icon_not_shown,navigate_notfound_url,assert_install_icon_not_shown,,,,,,,,,,
+navigate_pwa_in_scope,open_in_chrome,navigate_installable(site_a),install_omnibox_or_menu,navigate_pwa_in_scope,open_in_chrome,assert_tab_created,,,,,,,
+navigate_pwa_in_scope,uninstall_from_menu,navigate_installable(site_a),install_omnibox_or_menu,navigate_pwa_in_scope,uninstall_from_menu,list_apps,assert_app_not_in_list,,,,,,
+navigate_pwa_out_scope,close_custom_toolbar,navigate_installable(site_a),install_omnibox_or_menu,navigate_pwa_out_scope,close_custom_toolbar,assert_navigation_start_url,,,,,,,
+navigate_pwa_out_scope,open_in_chrome,navigate_installable(site_a),install_omnibox_or_menu,navigate_pwa_out_scope,open_in_chrome,assert_tab_created,,,,,,,
+navigate_pwa_out_scope,uninstall_from_menu,navigate_installable(site_a),install_omnibox_or_menu,navigate_pwa_out_scope,uninstall_from_menu,list_apps,assert_app_not_in_list,,,,,,
+remove_policy_app,assert_app_not_in_list,user_signin,add_policy_app_windowed_shortcut,remove_policy_app,list_apps,assert_app_not_in_list,,,,,,,
+remove_policy_app,navigate_browser_in_scope,user_signin,add_policy_app_tabbed_shortcut,remove_policy_app,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,
+set_app_badge,assert_app_badge_has_value,navigate_installable,install_omnibox_or_menu,set_app_badge,assert_app_badge_has_value,,,,,,,,
+set_app_badge,assert_platform_shortcut_not_exists,navigate_installable,set_app_badge,assert_platform_shortcut_not_exists,,,,,,,,,
+set_open_in_tab,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,chrome_update_with_bmo_migration,list_apps,launch_from_list,assert_tab_created,,,,
+set_open_in_tab,install_omnibox_or_menu,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,list_apps,launch_from_list,install_omnibox_or_menu,assert_window_created,,,,
+set_open_in_tab,launch_from_platform_shortcut,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,launch_from_platform_shortcut,assert_tab_created,,,,,,
+set_open_in_tab,list_apps,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,list_apps,assert_app_in_list_not_windowed,,,,,,
+set_open_in_tab,MISSING,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,list_apps,launch_from_list,assert_tab_created,,,,,
+set_open_in_tab,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,list_apps,set_open_in_tab,navigate_browser_in_scope,assert_install_icon_shown,,,,,,
+set_open_in_window,chrome_update_with_bmo_migration,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,chrome_update_with_bmo_migration,list_apps,launch_from_list,assert_window_created,,,,
+set_open_in_window,launch_from_omnibox_or_menu,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,launch_from_omnibox_or_menu,assert_window_created,,,,,,
+set_open_in_window,launch_from_platform_shortcut,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,launch_from_platform_shortcut,assert_window_created,,,,,,
+set_open_in_window,list_apps,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,list_apps,assert_app_in_list_windowed,,,,,,
+set_open_in_window,MISSING,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,list_apps,launch_from_list,assert_window_created,,,,,
+set_open_in_window,navigate_browser_in_scope,navigate_installable,install_create_shortcut_tabbed,list_apps,set_open_in_window,navigate_browser_in_scope,assert_launch_icon_shown,,,,,,
+switch_incognito_profile,assert_create_shortcut_not_shown,switch_incognito_profile,navigate_installable,assert_create_shortcut_not_shown,,,,,,,,,
+switch_incognito_profile,assert_install_icon_not_shown,switch_incognito_profile,navigate_installable,assert_install_icon_not_shown,,,,,,,,,
+switch_incognito_profile,assert_launch_icon_not_shown,navigate_installable,install_omnibox_or_menu,switch_incognito_profile,navigate_installable,assert_launch_icon_not_shown,,,,,,,
+switch_incognito_profile,MISSING,switch_incognito_profile,navigate_not_installable,,,,,,,,,,
+sync_turn_off,chrome_update,user_signin,navigate_not_installable,install_create_shortcut_tabbed,sync_turn_off,chrome_update,list_apps,assert_app_in_list_not_windowed,,,,,
+sync_turn_off,chrome_update,user_signin,navigate_installable,install_omnibox_or_menu,sync_turn_off,chrome_update,list_apps,assert_app_in_list_windowed,,,,,
+sync_turn_off,chrome_update_with_bmo_migration,user_signin,navigate_not_installable,install_create_shortcut_tabbed,sync_turn_off,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_windowed,,,,,
+sync_turn_off,chrome_update_with_bmo_migration,user_signin,navigate_installable,install_omnibox_or_menu,sync_turn_off,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_windowed,,,,,
+sync_turn_off,sync_turn_on,user_signin,navigate_installable,sync_turn_off,install_omnibox_or_menu,sync_turn_on,switch_profile_clients,list_apps,assert_app_in_list_not_locally_installed,install_locally,assert_app_in_list_windowed,,
+sync_turn_off,sync_turn_on,user_signin,navigate_not_installable,sync_turn_off,install_create_shortcut_tabbed,sync_turn_on,switch_profile_clients,list_apps,assert_app_in_list_not_locally_installed,install_locally,assert_app_in_list_not_windowed,,
+sync_turn_off,sync_turn_on,navigate_installable,install_omnibox_or_menu,user_signin,switch_profile_clients(user_a_client_2),sync_turn_off,launch_from_platform_shortcut,uninstall_from_menu,sync_turn_on,switch_profile_clients(user_a_client_1),assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list
+sync_turn_off,sync_turn_on,navigate_installable,install_omnibox_or_menu,user_signin,switch_profile_clients(user_a_client_2),sync_turn_off,list_apps,uninstall_from_app_list,sync_turn_on,switch_profile_clients(user_a_client_1),assert_platform_shortcut_not_exists,list_apps,assert_app_not_in_list
+uninstall_from_app_list,chrome_update,navigate_installable,install_omnibox_or_menu,list_apps,uninstall_from_app_list,chrome_update,list_apps,assert_app_not_in_list,,,,,
+uninstall_from_app_list,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,list_apps,uninstall_from_app_list,chrome_update_with_bmo_migration,list_apps,assert_app_not_in_list,,,,,
+uninstall_from_app_list,list_apps,navigate_installable,install_omnibox_or_menu,list_apps,uninstall_from_app_list,list_apps,assert_app_not_in_list,assert_platform_shortcut_not_exists,,,,,
+uninstall_from_app_list,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,list_apps,uninstall_from_app_list,navigate_browser_in_scope,assert_install_icon_shown,,,,,,
+uninstall_from_menu,chrome_update,navigate_installable,install_omnibox_or_menu,uninstall_from_menu,chrome_update,list_apps,assert_app_not_in_list,assert_platform_shortcut_not_exists,,,,,
+uninstall_from_menu,chrome_update_with_bmo_migration,navigate_installable,install_omnibox_or_menu,uninstall_from_menu,chrome_update_with_bmo_migration,list_apps,assert_app_not_in_list,assert_platform_shortcut_not_exists,,,,,
+uninstall_from_menu,list_apps,navigate_installable,install_omnibox_or_menu,uninstall_from_menu,list_apps,assert_app_not_in_list,assert_platform_shortcut_not_exists,,,,,,
+uninstall_from_menu,navigate_browser_in_scope,navigate_installable,install_omnibox_or_menu,uninstall_from_menu,navigate_browser_in_scope,assert_install_icon_shown,,,,,,,
+user_signin,chrome_update,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,chrome_update,list_apps,,,,,,
+user_signin,chrome_update,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,chrome_update,list_apps,assert_app_in_list_not_locally_installed,,,,
+user_signin,chrome_update,user_signin,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,chrome_update,list_apps,assert_app_in_list_not_locally_installed,,,,
+user_signin,chrome_update_with_bmo_migration,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,chrome_update_with_bmo_migration,list_apps,,,,,,
+user_signin,chrome_update_with_bmo_migration,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_locally_installed,,,,
+user_signin,chrome_update_with_bmo_migration,user_signin,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,chrome_update_with_bmo_migration,list_apps,assert_app_in_list_not_locally_installed,,,,
+user_signin,install_locally,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,list_apps,install_locally,assert_app_in_list_windowed,,,,,
+user_signin,install_locally,user_signin,navigate_not_installable,install_create_shortcut_tabbed,switch_profile_clients,list_apps,install_locally,assert_app_in_list_not_windowed,,,,,
+user_signin,install_locally,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally,assert_app_in_list_windowed,,,,
+user_signin,install_locally,user_signin,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally,assert_app_in_list_not_windowed,,,,
+user_signin,install_locally,user_signin,navigate_installable_site_a,install_omnibox_or_menu,delete_profile,user_signin,list_apps,install_locally_site_a,assert_app_in_list_windowed_site_a,,,,
+user_signin,install_locally,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,install_locally_site_a,assert_app_in_list_not_windowed_site_a,,,,
+user_signin,launch_from_list,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,launch_from_list,assert_tab_created,,,,
+user_signin,list_apps,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,list_apps,assert_app_in_list_not_locally_installed,,,,,,
+user_signin,list_apps,user_signin,navigate_not_installable,install_create_shortcut_tabbed,switch_profile_clients,list_apps,assert_app_in_list_not_locally_installed,,,,,,
+user_signin,list_apps,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,assert_app_in_list_not_locally_installed,,,,,
+user_signin,list_apps,user_signin,navigate_not_installable,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,assert_app_in_list_not_locally_installed,,,,,
+user_signin,list_apps,user_signin,navigate_installable_site_a,install_omnibox_or_menu,delete_profile,user_signin,list_apps,assert_app_in_list_not_locally_installed_site_a,,,,,
+user_signin,list_apps,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,delete_profile,user_signin,list_apps,assert_app_in_list_not_locally_installed_site_a,,,,,
+user_signin,navigate_browser_in_scope,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,list_apps,install_locally,navigate_browser_in_scope,assert_install_icon_shown,assert_launch_icon_shown,,,
+user_signin,navigate_browser_in_scope,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,navigate_browser_in_scope,assert_install_icon_shown,,,,,
+user_signin,navigate_browser_in_scope,user_signin,navigate_installable_site_a,install_omnibox_or_menu,delete_profile,user_signin,navigate_browser_in_scope_site_a,assert_install_icon_shown,,,,,
+user_signin,uninstall_from_app_list,user_signin,navigate_installable,install_omnibox_or_menu,switch_profile_clients,list_apps,uninstall_from_app_list,assert_app_not_in_list,,,,,
\ No newline at end of file
diff --git a/chrome/test/webapps/data/framework_actions_cros.csv b/chrome/test/webapps/data/framework_actions_cros.csv
new file mode 100644
index 0000000..f040115
--- /dev/null
+++ b/chrome/test/webapps/data/framework_actions_cros.csv
@@ -0,0 +1,33 @@
+add_policy_app_tabbed_shortcut_site_a
+add_policy_app_windowed_shortcut_site_a
+assert_app_not_in_list_site_a
+assert_install_icon_not_shown
+assert_install_icon_shown
+assert_launch_icon_not_shown
+assert_launch_icon_shown
+assert_tab_created
+assert_user_display_mode_browser_internal
+assert_user_display_mode_standalone_internal
+assert_manifest_display_mode_browser_internal
+assert_manifest_display_mode_minimal_internal
+assert_manifest_display_mode_standalone_internal
+assert_window_created
+close_pwa
+install_create_shortcut_tabbed
+install_internal_tabbed_site_a
+install_internal_windowed_site_a
+install_omnibox_or_menu
+launch_internal
+list_apps_internal
+navigate_browser_in_scope_site_a
+navigate_installable_site_a
+navigate_not_installable
+remove_policy_app_site_a
+set_open_in_tab_internal_site_a
+set_open_in_window_internal_site_a
+uninstall_internal_site_a
+sync_turn_on
+sync_turn_off
+switch_profile_clients_user_a_client_1
+switch_profile_clients_user_a_client_2
+user_signin
\ No newline at end of file
diff --git a/chrome/test/webapps/data/framework_actions_linux.csv b/chrome/test/webapps/data/framework_actions_linux.csv
new file mode 100644
index 0000000..0f1b52d
--- /dev/null
+++ b/chrome/test/webapps/data/framework_actions_linux.csv
@@ -0,0 +1,36 @@
+add_policy_app_tabbed_shortcut_site_a
+add_policy_app_windowed_shortcut_site_a
+assert_app_not_in_list_site_a
+assert_install_icon_not_shown
+assert_install_icon_shown
+assert_launch_icon_not_shown
+assert_launch_icon_shown
+assert_tab_created
+assert_user_display_mode_browser_internal
+assert_user_display_mode_standalone_internal
+assert_manifest_display_mode_browser_internal
+assert_manifest_display_mode_minimal_internal
+assert_manifest_display_mode_standalone_internal
+assert_window_created
+close_pwa
+install_create_shortcut_tabbed
+install_internal_tabbed_site_a
+install_internal_windowed_site_a
+install_omnibox_or_menu
+launch_internal
+list_apps_internal
+navigate_browser_in_scope_site_a
+navigate_installable_site_a
+navigate_not_installable
+remove_policy_app_site_a
+set_open_in_tab_internal_site_a
+set_open_in_window_internal_site_a
+uninstall_from_menu
+uninstall_internal_site_a
+sync_turn_on
+sync_turn_off
+switch_profile_clients_user_a_client_1
+switch_profile_clients_user_a_client_2
+user_signin
+install_locally_internal
+assert_app_not_locally_installed_internal
\ No newline at end of file
diff --git a/chrome/test/webapps/data/framework_actions_mac.csv b/chrome/test/webapps/data/framework_actions_mac.csv
new file mode 100644
index 0000000..0f1b52d
--- /dev/null
+++ b/chrome/test/webapps/data/framework_actions_mac.csv
@@ -0,0 +1,36 @@
+add_policy_app_tabbed_shortcut_site_a
+add_policy_app_windowed_shortcut_site_a
+assert_app_not_in_list_site_a
+assert_install_icon_not_shown
+assert_install_icon_shown
+assert_launch_icon_not_shown
+assert_launch_icon_shown
+assert_tab_created
+assert_user_display_mode_browser_internal
+assert_user_display_mode_standalone_internal
+assert_manifest_display_mode_browser_internal
+assert_manifest_display_mode_minimal_internal
+assert_manifest_display_mode_standalone_internal
+assert_window_created
+close_pwa
+install_create_shortcut_tabbed
+install_internal_tabbed_site_a
+install_internal_windowed_site_a
+install_omnibox_or_menu
+launch_internal
+list_apps_internal
+navigate_browser_in_scope_site_a
+navigate_installable_site_a
+navigate_not_installable
+remove_policy_app_site_a
+set_open_in_tab_internal_site_a
+set_open_in_window_internal_site_a
+uninstall_from_menu
+uninstall_internal_site_a
+sync_turn_on
+sync_turn_off
+switch_profile_clients_user_a_client_1
+switch_profile_clients_user_a_client_2
+user_signin
+install_locally_internal
+assert_app_not_locally_installed_internal
\ No newline at end of file
diff --git a/chrome/test/webapps/data/framework_actions_win.csv b/chrome/test/webapps/data/framework_actions_win.csv
new file mode 100644
index 0000000..0f1b52d
--- /dev/null
+++ b/chrome/test/webapps/data/framework_actions_win.csv
@@ -0,0 +1,36 @@
+add_policy_app_tabbed_shortcut_site_a
+add_policy_app_windowed_shortcut_site_a
+assert_app_not_in_list_site_a
+assert_install_icon_not_shown
+assert_install_icon_shown
+assert_launch_icon_not_shown
+assert_launch_icon_shown
+assert_tab_created
+assert_user_display_mode_browser_internal
+assert_user_display_mode_standalone_internal
+assert_manifest_display_mode_browser_internal
+assert_manifest_display_mode_minimal_internal
+assert_manifest_display_mode_standalone_internal
+assert_window_created
+close_pwa
+install_create_shortcut_tabbed
+install_internal_tabbed_site_a
+install_internal_windowed_site_a
+install_omnibox_or_menu
+launch_internal
+list_apps_internal
+navigate_browser_in_scope_site_a
+navigate_installable_site_a
+navigate_not_installable
+remove_policy_app_site_a
+set_open_in_tab_internal_site_a
+set_open_in_window_internal_site_a
+uninstall_from_menu
+uninstall_internal_site_a
+sync_turn_on
+sync_turn_off
+switch_profile_clients_user_a_client_1
+switch_profile_clients_user_a_client_2
+user_signin
+install_locally_internal
+assert_app_not_locally_installed_internal
\ No newline at end of file
diff --git a/chrome/test/webapps/data/manual_tests.csv b/chrome/test/webapps/data/manual_tests.csv
new file mode 100644
index 0000000..6ecd0e8
--- /dev/null
+++ b/chrome/test/webapps/data/manual_tests.csv
@@ -0,0 +1,28 @@
+# Testcase (from https://testtracker.googleplex.com/testplans/details/38998#),,,,,,,,,,,
+"1.1.1 Installing PWA through ""+"" on omnibox",navigate_installable,install_omnibox_or_menu,assert_window_created,list_apps,assert_app_in_list_windowed,assert_platform_shortcut_created,,,,,
+1.1.2 Installing PWA through Chrome Menu,navigate_installable,install_omnibox_or_menu,assert_window_created,list_apps,assert_app_in_list_windowed,assert_platform_shortcut_created,,,,,
+1.1.3 Installing PWA through creating shortcut,navigate_installable,install_create_shortcut_tabbed,assert_platform_shortcut_created,launch_from_platform_shortcut,assert_tab_created,,,,,,
+1.1.4 installing PWA through dragging url to chrome://apps,navigate_installable,drag_url_to_apps_list,launch_from_list,assert_tab_created,launch_from_omnibox_or_menu,assert_window_created,,,,,
+1.2.1 Launch PWA in Windows,navigate_installable,install_omnibox_or_menu,launch_from_platform_shortcut,assert_window_created,,,,,,,
+1.2.1 Launch PWA in Windows,navigate_installable,install_omnibox_or_menu,list_apps,launch_from_list,assert_window_created,,,,,,
+1.2.1 Launch PWA in Windows,navigate_installable,install_omnibox_or_menu,navigate_browser_in_scope,launch_from_omnibox_or_menu,assert_window_created,,,,,,
+1.3.1 Create PWA shortcuts open by window,N/A,navigate_installable(site_b),install_create_shortcut_windowed,assert_window_created,assert_platform_shortcut_created,assert_platform_shortcut_right_click_menu_has_actions,,,,,
+1.3.2 Shortcut Functionalities,N/A,navigate_installable(site_b),install_omnibox_or_menu,assert_window_created,close_pwa,click_platform_shortcut_right_click_menu_action,assert_window_created,,,,
+1.4.1 Full screen mode,N/A,,,,,,,,,,
+1.4.2 Application Menu options,N/A,,,,,,,,,,
+1.4.3 Application Titlebar,N/A,,,,,,,,,,
+1.5.1 PWA behaviour when closing the browser window,navigate_installable,install_omnibox_or_menu,close_browser,,,,,,,,
+1.5.2 PWA behaviour when exiting from browser,navigate_installable,install_omnibox_or_menu,assert_window_created,,,,,,,,
+1.5.3 PWA behaviour after an induced browser crash,N/A,navigate_installable,install_omnibox_or_menu,crash_browser,launch_from_platform_shortcut,assert_window_created,,,,,
+1.6.1 User login and log back using same profile,user_signin,navigate_installable,install_omnibox_or_menu,delete_profile,user_signin,list_apps,assert_app_in_list_windowed,,,,
+1.6.2 User login and logback using multiple profile,N/A,,,,,,,,,,
+1.6.3 User login and logout into the PC,navigate_installable,install_omnibox_or_menu,list_apps,assert_app_in_list_windowed,,,,,,,
+1.7.1 Uninstall PWA,navigate_installable,install_omnibox_or_menu,list_apps,uninstall_from_app_list,assert_app_not_in_list,assert_platform_shortcut_not_exists,,,,,
+1.7.2 Create shortcut + uninstall PWA,navigate_installable,install_create_shortcut_windowed,assert_window_created,uninstall_from_menu,navigate_installable,assert_install_icon_shown,,,,,
+1.7.3 Drag-n-drop pwa url in chrome://apps + uninstall pwa,navigate_installable,drag_url_to_apps_list,launch_from_list,uninstall_from_menu,assert_platform_shortcut_not_exists,list_apps ,assert_app_not_in_list,,,,
+1.8.1 PWA apps are present and functional after update - using PWA feature flag.,assert_old_chrome_version,navigate_installable,install_omnibox_or_menu,assert_window_created,assert_platform_shortcut_created,chrome_update_with_bmo_migration,assert_platform_shortcut_exists,list_apps,assert_app_in_list_windowed,launch_from_list,assert_window_created
+1.8.2 PWA apps are present and functional after update - using chrome updater.exe( WIN),N/A,,,,,,,,,,
+1.8.3 PWA apps are present and functional- by overinstall( Mac and Linux)),N/A,,,,,,,,,,
+1.8.4 PWA app shortcut is updated to yellow & black newt icon post manifest update,navigate_installable,install_omnibox_or_menu,manifest_update_icons,restart_chrome_for_manifest_update,launch_from_platform_shortcut,assert_platform_shortcut_icon_correct,,,,,
+1.9.1 Verify the Minimal UI buttons in case of right-to-left view.,N/A,,,,,,,,,,
+1.9.2 Verify that all the buttons on the window are functional.,navigate_installable(site_b),install_omnibox_or_menu,use_pwa_window_controls,,,,,,,,
\ No newline at end of file
diff --git a/chrome/test/webapps/data/partial_coverage_paths.csv b/chrome/test/webapps/data/partial_coverage_paths.csv
new file mode 100644
index 0000000..c177a28
--- /dev/null
+++ b/chrome/test/webapps/data/partial_coverage_paths.csv
@@ -0,0 +1,44 @@
+#,Action/s that can partially cover the input action/s
+user_signin; add_policy_app_tabbed_no_shortcut,add_policy_app_internal_tabbed_no_shortcut
+user_signin; add_policy_app_tabbed_shortcut,add_policy_app_internal_tabbed_shortcut
+user_signin; add_policy_app_windowed_no_shortcut,add_policy_app_internal_windowed_no_shortcut
+user_signin; add_policy_app_windowed_shortcut,add_policy_app_internal_windowed_shortcut
+list_apps; assert_app_in_list,assert_app_installed_internal
+list_apps; assert_app_in_list_locally_installed,assert_app_locally_installed_internal
+list_apps; assert_app_not_in_list,assert_app_not_installed_internal
+list_apps; assert_app_in_list_not_locally_installed,assert_app_not_locally_installed_internal
+list_apps; assert_app_in_list_not_windowed,assert_user_display_mode_browser_internal
+launch_from_platform_shortcut; assert_tab_created,assert_user_display_mode_browser_internal
+launch_from_platform_shortcut; assert_window_created; assert_window_display_minimal,assert_manifest_display_mode_minimal_internal
+list_apps; assert_app_in_list_windowed,assert_user_display_mode_standalone_internal
+launch_from_platform_shortcut; assert_window_created; assert_window_display_standalone,assert_manifest_display_mode_standalone_internal
+launch_from_platform_shortcut; assert_window_created,assert_user_display_mode_standalone_internal
+assert_window_icon_correct,assert_icon_correct_internal
+list_apps; assert_app_in_list_icon_correct,assert_icon_correct_internal
+assert_platform_shortcut_icon_correct,assert_icon_correct_internal
+assert_install_icon_not_shown,assert_not_installable_internal
+launch_from_platform_shortcut; assert_theme_color,assert_theme_color_internal
+list_apps; launch_from_list; assert_theme_color,assert_theme_color_internal
+close_pwa; launch_from_platform_shortcut; close_pwa,force_manifest_update_internal
+close_pwa; list_apps; launch_from_list; close_pwa,force_manifest_update_internal
+install_omnibox_or_menu,install_create_shortcut_windowed
+navigate_not_installable; install_create_shortcut_tabbed,install_internal_tabbed
+navigate_installable; install_create_shortcut_tabbed,install_internal_tabbed
+navigate_installable; install_omnibox_or_menu,install_internal_windowed
+navigate_installable; install_create_shortcut_windowed,install_internal_windowed
+navigate_not_installable; install_create_shortcut_windowed,install_internal_windowed
+install_create_shortcut_windowed,install_omnibox_or_menu
+list_apps; launch_from_list,launch_internal
+navigate_browser_in_scope; launch_from_omnibox_or_menu,launch_internal
+launch_from_platform_shortcut,launch_internal
+list_apps,list_apps_internal
+remove_policy_app,remove_policy_app_internal
+close_pwa; launch_from_platform_shortcut; close_pwa,restart_chrome_for_manifest_update
+close_pwa; list_apps; launch_from_list; close_pwa,restart_chrome_for_manifest_update
+list_apps; set_open_in_tab,set_open_in_tab_internal
+list_apps; set_open_in_window,set_open_in_window_internal
+list_apps; uninstall_from_app_list,uninstall_internal
+launch_from_platform_shortcut; uninstall_from_menu,uninstall_internal
+list_apps; launch_from_list; uninstall_from_menu,uninstall_internal
+install_locally,install_locally_internal
+delete_profile; user_signin,switch_profile_clients_user_a_client_2
\ No newline at end of file
diff --git a/chrome/test/webapps/download_data_from_sheet.sh b/chrome/test/webapps/download_data_from_sheet.sh
new file mode 100755
index 0000000..8a49be7
--- /dev/null
+++ b/chrome/test/webapps/download_data_from_sheet.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -o errexit  # Stop the script on the first error.
+set -o nounset  # Catch un-initialized variables.
+
+SHEET_URL="https://docs.google.com/spreadsheets/u/1/d/1d3iAOAnojp4_WrPky9exz1-mjkeulOJVUav5QYG99MQ/export"
+COVERAGE_TESTS_GID=2008870403
+ACTIONS_GID=1864725389
+PARTIAL_COVERAGE_GID=452077264
+AUTOMATED_TESTS_GID=1894585254
+MANUAL_TESTS_GID=1424278080
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+
+curl -L "${SHEET_URL}?gid=${COVERAGE_TESTS_GID}&format=csv" \
+  > "${DIR}/data/coverage_required.csv"
+curl -L "${SHEET_URL}?gid=${ACTIONS_GID}&format=csv" \
+  > "${DIR}/data/actions.csv"
+curl -L "${SHEET_URL}?gid=${PARTIAL_COVERAGE_GID}&format=csv" \
+  > "${DIR}/data/partial_coverage_paths.csv"
+curl -L "${SHEET_URL}?gid=${AUTOMATED_TESTS_GID}&format=csv" \
+  > "${DIR}/data/automated_tests.csv"
+curl -L "${SHEET_URL}?gid=${MANUAL_TESTS_GID}&format=csv" \
+  > "${DIR}/data/manual_tests.csv"
+
+# Note: This script does NOT populate
+# //chrome/test/web_apps/data/framework_actions_*.csv. This is intentional.
+# These files should be updated manually by the person adding actions to the
+# integration test framework.
+
+echo "Done!"
diff --git a/chrome/test/webapps/file_reading.py b/chrome/test/webapps/file_reading.py
new file mode 100755
index 0000000..43c1f74f
--- /dev/null
+++ b/chrome/test/webapps/file_reading.py
@@ -0,0 +1,410 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Parsing logic to read files for the Web App testing framework.
+"""
+
+import logging
+import re
+from typing import Dict, List, Set, Tuple
+
+from classes import Action
+from classes import CoverageTest
+from classes import PartialCoverageAddition
+
+
+def HumanFriendlyNameToCanonicalActionName(
+        human_friendly_action_name: str,
+        action_base_name_to_default_param: Dict[str, str]):
+    """
+    Converts a human-friendly action name and turns into the format compatible
+    with this testing framework. This does two things:
+    1) Resolving specified parameters, from "()" format into a "_". For example,
+       "action(with_parameter)" turns into "action_with_parameter".
+    2) Resolving parameterless actions (that have a default parameter) to
+       include the default parameter. For example, if
+       |action_base_name_to_default_param| contains an entry for
+       |human_friendly_action_name|, then that entry is appended to the action.
+       "action" and {"action": "default_param"} will respectively will return
+       "action_default_param".
+    If neither of those cases apply, then the |human_friendly_action_name| is
+    returned.
+    """
+    human_friendly_action_name = human_friendly_action_name.strip()
+    if human_friendly_action_name in action_base_name_to_default_param:
+        # Handle default parameter.
+        human_friendly_action_name += "_" + action_base_name_to_default_param[
+            human_friendly_action_name]
+    elif '(' in human_friendly_action_name:
+        # Handle parameter being specified.
+        human_friendly_action_name = human_friendly_action_name.replace(
+            "(", "_").rstrip(")")
+    return human_friendly_action_name
+
+
+def ReadActionsFile(
+        actions_csv_file
+) -> Tuple[Dict[str, Action], Dict[str, str], Dict[str, Set[str]]]:
+    """Reads the actions comma-separated-values file.
+
+    If parameters are specified for an action in the file, then one action is
+    added to the results dictionary per action_base_name + parameter
+    combination. A parameter marked with a "*" is considered the default
+    parameter for that action.
+
+    See the README.md for more information about actions and action templates.
+
+    Args:
+        actions_csv_file: The comma-separated-values file read to parse all
+                          actions.
+
+    Returns (actions_by_name,
+             action_base_name_to_default_param,
+             action_base_name_to_all_params):
+        actions_by_name:
+            Index of all actions by action name.
+        action_base_name_to_default_param:
+            Index of action base names to the default parameter. Only populated
+            for actions with default parameters.
+        action_base_name_to_all_params:
+            Index of action base names to all the parameters, if it has
+            parameters.
+
+    Raises:
+        ValueError: The input file is invalid.
+    """
+    actions_by_name = {}
+    action_base_name_to_default_param = {}
+    action_base_name_to_all_params = {}
+    for i, row in enumerate(actions_csv_file):
+        if not row:
+            continue
+        if row[0].startswith("#"):
+            continue
+        if len(row) < 3:
+            raise ValueError(f"Row {i!r} does not contain enough entries. "
+                             f"Got [{','.join(row)!r}].")
+        action_base_name = row[0].strip()
+        if not re.fullmatch(r'\w+', action_base_name):
+            raise ValueError(
+                f"Invald action base name {action_base_name!r} on "
+                f"row {i!r}.")
+        params = [param.strip() for param in row[1].split("| ")]
+        is_state_check_text = row[2].strip()
+        if is_state_check_text not in ("", "Y"):
+            raise ValueError(
+                f"Row {i!r} signifying if {action_base_name!r} is an "
+                f"state_check action must be either empty or 'Y'.")
+        is_state_check = is_state_check_text == "Y"
+        if not params:
+            params = [""]
+        action_base_name_to_all_params[action_base_name] = set()
+        for param in params:
+            if not re.fullmatch(r'(\w+\*?)|', param):
+                raise ValueError(f"Invald action param name {param!r}) on row "
+                                 f"{i!r}.")
+            if "*" in param:
+                action_base_name_to_default_param[
+                    action_base_name] = param.rstrip("*")
+            param = param.rstrip("*")
+            name = action_base_name
+            if param != "":
+                action_base_name_to_all_params[action_base_name].add(param)
+                name += "_" + param
+            if name in actions_by_name:
+                raise ValueError(
+                    f"Cannot add duplicate action {name!r} on row "
+                    f"{i!r}")
+            action = Action(name, action_base_name, is_state_check)
+            actions_by_name[action.name] = action
+    return (actions_by_name, action_base_name_to_default_param,
+            action_base_name_to_all_params)
+
+
+def ReadCoverageTestsFile(coverage_csv_file, actions: Dict[str, Action],
+                          action_base_name_to_default_param: Dict[str, str]
+                          ) -> List[CoverageTest]:
+    """Reads the coverage tests comma-separated-values file.
+
+    The coverage tests file can have blank entries in the test row, and does not
+    have test names.
+
+    Args:
+        coverage_csv_file: The comma-separated-values file with all coverage
+                           tests.
+        actions: An index of action name to Action
+        action_base_name_to_default_param: An index of action base name to
+                                           default parameter, if there is one.
+
+    Returns:
+        A list of CoverageTests read from the file.
+
+    Raises:
+        ValueError: The input file is invalid.
+    """
+    missing_actions = []
+    required_coverage_tests = []
+    for i, row in enumerate(coverage_csv_file):
+        if not row:
+            continue
+        if row[0].startswith("#"):
+            continue
+        if len(row) < 3:
+            raise ValueError(f"Row {i!r} does not have test actions.")
+        coverage_test = CoverageTest(str(i))
+        required_coverage_tests.append(coverage_test)
+        for action_name in row[2:]:
+            action_name = action_name.strip()
+            if "," in action_name:
+                raise ValueError(f"Actions on row {i!r} cannot have "
+                                 f"multiple parameters: {action_name!r}")
+            if action_name == "":
+                continue
+            action_name = HumanFriendlyNameToCanonicalActionName(
+                action_name, action_base_name_to_default_param)
+            if action_name not in actions:
+                missing_actions.append(action_name)
+                logging.error(f"Could not find action on row {i!r}: "
+                              f"{action_name!r}")
+                continue
+            coverage_test.actions.append(actions[action_name])
+    if missing_actions:
+        raise ValueError(f"Actions missing from actions dictionary: "
+                         f"{', '.join(missing_actions)!r}")
+    return required_coverage_tests
+
+
+def ReadNamedTestsFile(tests_csv_file, actions: Dict[str, Action],
+                       action_base_name_to_default_param: Dict[str, str]
+                       ) -> List[CoverageTest]:
+    """Reads the tests comma-separated-values file.
+
+    Test rows are expected to begin with the test name. Tests rows with no
+    actions are ignored, and if a test has "N/A" as an action, then no more
+    actions are parsed for that test.
+
+    Args:
+        tests_csv_file: The comma-separated-values file with tests.
+        actions: An index of action name to Action.
+        action_base_name_to_default_param: An index of action base name to
+                                           default parameter, if there is one.
+    Returns:
+        A list of CoverageTests read from the file.
+
+    Raises:
+        ValueError: The input file is invalid.
+    """
+    missing_actions = []
+    tests = []
+    # Create a test for each row, and populate all actions for that test.
+    for row in tests_csv_file:
+        if not row:
+            continue
+        if row[0].startswith("#"):
+            continue
+        if len(row) < 2:
+            continue
+        test = CoverageTest(row[0])
+        for action_name in row[1:]:
+            action_name = action_name.strip()
+            if action_name == "" or action_name == "N/A":
+                break
+            action_name = HumanFriendlyNameToCanonicalActionName(
+                action_name, action_base_name_to_default_param)
+            if action_name not in actions:
+                missing_actions.append(action_name)
+                logging.error(f"Could not find action: {action_name!r}")
+                continue
+            test.actions.append(actions[action_name])
+        if test.actions:
+            tests.append(test)
+    if missing_actions:
+        raise ValueError(f"Actions missing from actions dictionary: "
+                         f"{', '.join(missing_actions)!r}")
+    return tests
+
+
+def ValidatePartialPaths(partial_paths: List[PartialCoverageAddition]):
+    """Validates that all |partial_paths| are valid:
+    1) They contain actions, and
+    2) They don't share an initial non-state-check action. This means
+       that the first action in the input and output actions that is
+       not a "state check" action must not be the same.
+    """
+    for partial_path in partial_paths:
+        assert len(partial_path.input_actions) > 0
+        assert len(partial_path.output_actions) > 0
+
+        def FindFirstNonStateCheckAction(actions: List[Action]) -> Action:
+            for action in actions:
+                if not action.is_state_check:
+                    return action
+            return None
+
+        # Check that the paths don't both start with the same non-state_check
+        # action.
+        first_input_non_state_check_action = FindFirstNonStateCheckAction(
+            partial_path.input_actions)
+        first_output_non_state_check_action = FindFirstNonStateCheckAction(
+            partial_path.output_actions)
+        if (first_input_non_state_check_action is not None
+                and first_output_non_state_check_action is not None
+                and first_input_non_state_check_action is
+                first_output_non_state_check_action):
+            raise ValueError(
+                f"Partial path addition cannot share the same first "
+                f"non-state_check action in the input and output actions "
+                f"{first_input_non_state_check_action.name!r}.")
+
+
+def ReadPartialCoveragePathsFile(
+        partial_paths_csv_file, actions: Dict[str, Action],
+        action_base_name_to_all_params: Dict[str, Set[str]]
+) -> List[PartialCoverageAddition]:
+    """Reads the partial coverage paths file.
+
+    The file is expected to have 2 columns per row, each one a semicolon-
+    separated list of action names. Actions can be non-parameterized, in which
+    case a partial path for every parameter will be added. If multiple actions
+    have unspecified parameters (on input & output), then parameters are chosen
+    as an intersection of all parameter options. If no parameters are possible,
+    an exception is raised.
+
+    The paths cannot share initial non-state_check actions.
+
+    Args:
+        partial_paths_csv_file: The comma-separated-values file with all partial
+                                paths.
+        actions: An index of action name to Action.
+        action_base_name_to_all_params: An index of action base name to all
+                                        parameters.
+
+    Returns:
+        A list of PartialCoverageAddition objects.
+
+    Raises:
+        ValueError: The input file is invalid.
+    """
+    partial_paths: List[PartialCoverageAddition] = []
+    for row in partial_paths_csv_file:
+        if not row:
+            continue
+        if row[0].startswith("#"):
+            continue
+        if len(row) < 2:
+            continue
+        input_action_names = [name.strip() for name in row[0].split(';')]
+        output_action_names = [name.strip() for name in row[1].split(';')]
+
+        if not input_action_names or not output_action_names:
+            raise ValueError(f"Input and output actions must be populated: "
+                             f"[{'; '.join(row)!r}]")
+
+        # To support parameters, find all of the common parameters for all
+        # actions that have parameters, and output a separate partial path
+        # per parameter matching.
+        params_needed = False
+        all_params = None
+
+        for action_name in input_action_names + output_action_names:
+            if action_name in actions:
+                continue
+            if action_name not in action_base_name_to_all_params:
+                raise ValueError(
+                    f"Could not find action or action base {action_name!r}")
+            params_needed = True
+            action_params = action_base_name_to_all_params[action_name]
+            assert isinstance(action_params, set)
+            all_params = (action_params if all_params == None else
+                          all_params.intersection(action_params))
+
+        if params_needed and not all_params:
+            raise ValueError(
+                f"Could not find common parameters in input actions "
+                f"[{', '.join(input_action_names)!r}] and output actions "
+                f"[{', '.join(output_action_names)!r}].")
+
+        if not params_needed:
+            partial = PartialCoverageAddition()
+            partial.input_actions = ([
+                actions[action_name] for action_name in input_action_names
+            ])
+            partial.output_actions = ([
+                actions[action_name] for action_name in output_action_names
+            ])
+            partial_paths.append(partial)
+            continue
+
+        assert isinstance(all_params, set)
+        for param in all_params:
+            input_action_names_with_param = []
+            output_action_names_with_param = []
+            for action_name in input_action_names:
+                if action_name not in actions:
+                    action_name = action_name + "_" + param
+                if action_name not in actions:
+                    raise ValueError(f"Could not find action {action_name!r}")
+                input_action_names_with_param.append(action_name)
+            for action_name in output_action_names:
+                if action_name not in actions:
+                    action_name = action_name + "_" + param
+                if action_name not in actions:
+                    raise ValueError(f"Could not find action {action_name!r}")
+                output_action_names_with_param.append(action_name)
+            partial = PartialCoverageAddition()
+            partial.input_actions = ([
+                actions[action_name]
+                for action_name in input_action_names_with_param
+            ])
+            partial.output_actions = ([
+                actions[action_name]
+                for action_name in output_action_names_with_param
+            ])
+            partial_paths.append(partial)
+    ValidatePartialPaths(partial_paths)
+    return partial_paths
+
+
+def ReadFrameworkActions(framework_actions_csv_file,
+                         actions: Dict[str, Action],
+                         action_base_name_to_default_param: Dict[str, str]
+                         ) -> Dict[str, Action]:
+    """Reads the supported framework actions file.
+
+    Ignores blank actions.
+
+    Args:
+        framework_actions_csv_file: The comma-separated-values file with all
+                                    supported framework actions.
+        actions: An index of action name to Action.
+        action_base_name_to_default_param: An index of action base name default
+                                           parameter.
+
+    Returns:
+        A dictionary of actions supported by the testing framework.
+
+    Raises:
+        ValueError: The input file is invalid.
+    """
+    missing_actions = []
+    framework_actions = {}
+    for row in framework_actions_csv_file:
+        if not row:
+            continue
+        if row[0].startswith("#"):
+            continue
+        action_name = row[0].strip()
+        if not action_name:
+            continue
+        action_name = HumanFriendlyNameToCanonicalActionName(
+            action_name, action_base_name_to_default_param)
+        if action_name not in actions:
+            missing_actions.append(action_name)
+            logging.error(f"Could not find action: {action_name!r}")
+            continue
+        framework_actions[action_name] = actions[action_name]
+    if missing_actions:
+        raise ValueError(f"Actions missing from actions dictionary: "
+                         f"{', '.join(missing_actions)!r}")
+    return framework_actions
diff --git a/chrome/test/webapps/generate_framework_tests_and_coverage.py b/chrome/test/webapps/generate_framework_tests_and_coverage.py
new file mode 100755
index 0000000..7601796
--- /dev/null
+++ b/chrome/test/webapps/generate_framework_tests_and_coverage.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium 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 used to generate the tests definitions for Web App testing framework.
+See the README.md file in this directory for more information.
+
+Usage: python3 chrome/test/web_apps/generate_framework_tests_and_coverage.py
+"""
+
+import argparse
+import logging
+import os
+import csv
+
+from graph_analysis import AddPartialPaths
+from graph_analysis import CreateFullCoverageActionGraph
+from graph_analysis import GenerateFrameworkTests
+from graph_analysis import GenerateGraphvizDotFile
+from graph_analysis import GenerateCoverageFileAndPercents
+from graph_analysis import TrimGraphToAllowedActions
+from classes import ActionCoverage
+from classes import Action
+from classes import ActionNode
+from classes import CoverageTest
+from classes import PartialCoverageAddition
+from file_reading import ReadActionsFile
+from file_reading import ReadCoverageTestsFile
+from file_reading import ReadFrameworkActions
+from file_reading import ReadPartialCoveragePathsFile
+from file_reading import ReadNamedTestsFile
+
+# These actions, if detected, will add a test to a separate file.
+SYNC_ACTIONS = [
+    "switch_profile_clients_user_a_client_1",
+    "switch_profile_clients_user_a_client_2", "sync_turn_on", "sync_turn_off"
+]
+
+GENERATED_FILE_HEADER = ("# DO NOT EDIT - THIS IS A GENERATED FILE.\n" +
+                         "# See /chrome/test/web_app/README.md for more info.")
+
+
+def main():
+    parser = argparse.ArgumentParser(description='WebApp Test List Processor')
+    parser.add_argument('-v',
+                        dest='v',
+                        action='store_true',
+                        help='Include info logging.',
+                        required=False)
+
+    parser.add_argument('--graphs',
+                        dest='graphs',
+                        action='store_true',
+                        help='Output dot graphs from all steps.',
+                        required=False)
+    parser.add_argument(
+        '--ignore_audited',
+        dest='ignore_audited',
+        action='store_true',
+        help='Ignore the audited manual and automated test data',
+        required=False)
+    options = parser.parse_args()
+    logging.basicConfig(level=logging.INFO if options.v else logging.WARN,
+                        format='[%(asctime)s %(levelname)s] %(message)s',
+                        datefmt='%H:%M:%S')
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    output_dir = script_dir + "/output"
+    actions_file = script_dir + "/data/actions.csv"
+    coverage_required_file = script_dir + "/data/coverage_required.csv"
+    partial_coverage_file = script_dir + "/data/partial_coverage_paths.csv"
+    audited_manual_tests_file = script_dir + "/data/manual_tests.csv"
+    audited_automated_tests_file = script_dir + "/data/automated_tests.csv"
+    partial_coverage_file = script_dir + "/data/partial_coverage_paths.csv"
+    framework_actions_file_base = script_dir + "/data/framework_actions_"
+
+    actions_csv = csv.reader(open(actions_file), delimiter=',')
+    coverage_csv = csv.reader(open(coverage_required_file), delimiter=',')
+    partial_csv = csv.reader(open(partial_coverage_file), delimiter=',')
+
+    (actions, action_base_name_to_default_param,
+     action_base_name_to_all_params) = ReadActionsFile(actions_csv)
+    required_coverage_tests = ReadCoverageTestsFile(
+        coverage_csv, actions, action_base_name_to_default_param)
+    partial_paths = ReadPartialCoveragePathsFile(
+        partial_csv, actions, action_base_name_to_all_params)
+    partial_csv = csv.reader(open(partial_coverage_file), delimiter=',')
+    reversed_partial_paths = ReadPartialCoveragePathsFile(
+        partial_csv, actions, action_base_name_to_all_params)
+    for path in reversed_partial_paths:
+        path.Reverse()
+
+    if options.graphs:
+        coverage_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(coverage_root_node,
+                                      required_coverage_tests)
+        AddPartialPaths(coverage_root_node, partial_paths)
+        coverage_graph = GenerateGraphvizDotFile(coverage_root_node)
+        output_coverage_graph_file_name = (output_dir +
+                                           "/coverage_required_graph.dot")
+        coverage_graph_file = open(output_coverage_graph_file_name, 'w')
+        coverage_graph_file.write(GENERATED_FILE_HEADER + "\n")
+        coverage_graph_file.write(coverage_graph)
+        coverage_graph_file.close()
+
+    for platform in ['linux', 'mac', 'cros', 'win']:
+        framework_actions_csv = csv.reader(open(framework_actions_file_base +
+                                                platform + ".csv"),
+                                           delimiter=',')
+        framework_actions = ReadFrameworkActions(
+            framework_actions_csv, actions, action_base_name_to_default_param)
+
+        # Create the coverage graph, then prune all unsupported actions for
+        # this platform.
+        coverage_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(coverage_root_node,
+                                      required_coverage_tests)
+        AddPartialPaths(coverage_root_node, partial_paths)
+        TrimGraphToAllowedActions(coverage_root_node, framework_actions)
+
+        if options.graphs:
+            graph = GenerateGraphvizDotFile(coverage_root_node)
+            output_graph_file_name = (output_dir + "/framework_test_graph_" +
+                                      platform + ".dot")
+            graph_file = open(output_graph_file_name, 'w')
+            graph_file.write(GENERATED_FILE_HEADER + "\n")
+            graph_file.write(graph)
+            graph_file.close()
+
+        # Write the framework tests. All tests that involve 'sync' actions must
+        # be in a separate file.
+        output_tests_file_name = (output_dir + "/framework_tests_" + platform +
+                                  ".csv")
+        output_tests_sync_file_name = (output_dir + "/framework_tests_sync_" +
+                                       platform + ".csv")
+        framework_file = open(output_tests_file_name, 'w')
+        framework_sync_file = open(output_tests_sync_file_name, 'w')
+        framework_file.write(GENERATED_FILE_HEADER + "\n")
+        framework_sync_file.write(GENERATED_FILE_HEADER + "\n")
+        paths = GenerateFrameworkTests(coverage_root_node,
+                                       required_coverage_tests)
+        for path in paths:
+            all_actions_in_path = []
+            for node in path[1:]:  # Skip the root node
+                all_actions_in_path.append(node.action)
+                all_actions_in_path.extend(node.state_check_actions.values())
+            action_names = [action.name for action in all_actions_in_path]
+            file = framework_file
+            for action_name in action_names:
+                if action_name in SYNC_ACTIONS:
+                    file = framework_sync_file
+                    break
+            file.write("," + ",".join(action_names) + "\n")
+        framework_file.close()
+        framework_sync_file.close()
+
+        # Analyze all tests to generate coverage
+        test_files = [output_tests_file_name, output_tests_sync_file_name]
+        if not options.ignore_audited:
+            test_files.extend(
+                [audited_automated_tests_file, audited_manual_tests_file])
+        tests = []
+        for tests_file in test_files:
+            tests_csv = csv.reader(open(tests_file), delimiter=',')
+            tests.extend(
+                ReadNamedTestsFile(tests_csv, actions,
+                                   action_base_name_to_default_param))
+        tests_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(tests_root_node, tests)
+        AddPartialPaths(tests_root_node, reversed_partial_paths)
+
+        (coverage_file_text, full_coverage,
+         partial_coverage) = GenerateCoverageFileAndPercents(
+             required_coverage_tests, tests_root_node)
+
+        coverage_table_file = output_dir + "/coverage_" + platform + ".tsv"
+        coverage_file = open(coverage_table_file, 'w')
+        coverage_file.write(GENERATED_FILE_HEADER + "\n")
+        coverage_file.write(
+            "# Full Coverage: {:.2f}, Partial Coverage: {:.2f}\n".format(
+                full_coverage, partial_coverage))
+        coverage_file.write(coverage_file_text)
+        coverage_file.close()
+
+        if options.graphs:
+            coverage_graph = GenerateGraphvizDotFile(tests_root_node)
+            coverage_graph_filename = (output_dir + "/coverage_graph_" +
+                                       platform + ".dot")
+            coverage_graph_file = open(coverage_graph_filename, 'w')
+            coverage_graph_file.write(GENERATED_FILE_HEADER + "\n")
+            for graph_line in coverage_graph:
+                coverage_graph_file.write(graph_line + "\n")
+            coverage_graph_file.close()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/chrome/test/webapps/graph_analysis.py b/chrome/test/webapps/graph_analysis.py
new file mode 100755
index 0000000..79ea7a4
--- /dev/null
+++ b/chrome/test/webapps/graph_analysis.py
@@ -0,0 +1,477 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Graph analysis functions for the testing framework.
+"""
+
+import logging
+from typing import Dict, List, Set, Tuple
+
+from classes import ActionCoverage
+from classes import Action
+from classes import ActionNode
+from classes import CoverageTest
+from classes import PartialCoverageAddition
+
+
+def CreateFullCoverageActionGraph(root_node: ActionNode,
+                                  tests: List[CoverageTest]) -> ActionNode:
+    assert isinstance(root_node, ActionNode)
+    assert isinstance(tests, list)
+    for test in tests:
+        assert isinstance(test, CoverageTest)
+        parent = root_node
+        for action in test.actions:
+            assert isinstance(action, Action)
+            assert parent is not None
+            if action.is_state_check:
+                parent.AddStateCheckAction(action, ActionCoverage.FULL)
+            else:
+                node = None
+                if action.name in parent.children:
+                    node = parent.children[action.name]
+                else:
+                    node = ActionNode(action)
+                    parent.AddChild(node)
+                node.coverage = ActionCoverage.FULL
+                node.full_coverage_tests.add(test)
+                parent = node
+
+
+def AddPartialPaths(root_node: ActionNode,
+                    partial_paths: List[PartialCoverageAddition]) -> None:
+    assert isinstance(root_node, ActionNode)
+    assert isinstance(partial_paths, list)
+
+    partial_path_existence = set()
+    for partial_path in partial_paths:
+        partial_path_existence.add(partial_path.input_actions[0].name)
+
+    # Returns the node that is the result of following the given action list
+    # in the graph. Also accumulates all fully covered and partially covered
+    # in the path. These tests are what the new path will partially cover.
+    def FollowGraph(start: ActionNode, action_list: List[ActionNode]
+                    ) -> Tuple[ActionNode, Set[Action]]:
+        assert isinstance(start, ActionNode)
+        assert len(action_list) > 0
+        curr_node = start
+        tests_to_partial_cover = curr_node.full_coverage_tests.copy()
+        tests_to_partial_cover.update(curr_node.partial_coverage_tests)
+        for action in action_list:
+            assert isinstance(action, Action)
+            if action.name in curr_node.children:
+                curr_node = curr_node.children[action.name]
+                tests_to_partial_cover.update(curr_node.full_coverage_tests)
+                tests_to_partial_cover.update(curr_node.partial_coverage_tests)
+            elif action.name not in curr_node.state_check_actions:
+                return (None, None)
+        return (curr_node, tests_to_partial_cover)
+
+    def MergeStateCheckCoverage(
+            a_state_check_coverage: Dict[str, ActionCoverage],
+            b_state_check_coverage: Dict[str, ActionCoverage]
+    ) -> Dict[str, ActionCoverage]:
+        def MergePartialEntry(coverage_a: ActionCoverage,
+                              coverage_b: ActionCoverage):
+            if (coverage_a == ActionCoverage.FULL
+                    or coverage_b == ActionCoverage.FULL):
+                return ActionCoverage.FULL
+            return ActionCoverage.PARTIAL
+
+        # Start with symmetric difference
+        result = ({
+            k: a_state_check_coverage.get(k, b_state_check_coverage.get(k))
+            for k in (a_state_check_coverage.keys()
+                      ^ b_state_check_coverage.keys())
+        })
+        # Update with `MergePartialEntry()` applied to the intersection
+        result.update({
+            k: MergePartialEntry(a_state_check_coverage[k],
+                                 b_state_check_coverage[k])
+            for k in (a_state_check_coverage.keys()
+                      & b_state_check_coverage.keys())
+        })
+        return result
+
+    def MergeNodes(node_a: ActionNode, node_b: ActionNode) -> ActionNode:
+        assert not node_a.dead
+        assert not node_b.dead
+        if node_a == node_b:
+            return node_a
+        assert node_a.action.name == node_b.action.name
+        logging.info("Merging nodes with paths:\n{" +
+                     node_a.GetGraphPathStr() + "},\n{" +
+                     node_b.GetGraphPathStr() + "}")
+        new_node = ActionNode(node_a.action)
+        new_node.coverage = (ActionCoverage.FULL
+                             if node_a.coverage == ActionCoverage.FULL
+                             or node_b.coverage == ActionCoverage.FULL else
+                             ActionCoverage.PARTIAL)
+        new_node.state_check_actions = node_a.state_check_actions
+        new_node.state_check_actions.update(node_b.state_check_actions)
+        new_node.state_check_actions_coverage = MergeStateCheckCoverage(
+            node_a.state_check_actions_coverage,
+            node_b.state_check_actions_coverage)
+        new_node.full_coverage_tests = node_a.full_coverage_tests
+        new_node.full_coverage_tests.update(node_b.full_coverage_tests)
+        new_node.partial_coverage_tests = node_a.partial_coverage_tests
+        new_node.partial_coverage_tests.update(node_b.partial_coverage_tests)
+        children_a = node_a.children.copy()
+        children_b = node_b.children.copy()
+        parents_a = node_a.parents.copy()
+        parents_b = node_b.parents.copy()
+
+        # Fully remove the merging nodes from the graph
+        for child in list(children_a.values()) + list(children_b.values()):
+            if new_node.action.name in child.parents:
+                del child.parents[new_node.action.name]
+        for parent in list(parents_a.values()) + list(parents_b.values()):
+            if new_node.action.name in parent.children:
+                del parent.children[new_node.action.name]
+
+        # Merge children.
+        # Start by adding the non-intersecting children to the dictionary.
+        children = ({
+            k: children_a.get(k, children_b.get(k)).ResolveToAliveNode()
+            for k in (children_a.keys() ^ children_b.keys())
+        })
+        # For all children that are the same, they must be merged.
+        children.update({
+            k: MergeNodes(children_a[k].ResolveToAliveNode(),
+                          children_b[k].ResolveToAliveNode())
+            for k in (children_a.keys() & children_b.keys())
+        })
+
+        # Merge parents.
+        # Start by adding the non-intersecting parents to the dictionary.
+        parents = ({
+            k: parents_a.get(k, parents_b.get(k)).ResolveToAliveNode()
+            for k in (parents_a.keys() ^ parents_b.keys())
+        })
+        # For all parents that are the same, they must be merged.
+        parents.update({
+            k: MergeNodes(parents_a[k].ResolveToAliveNode(),
+                          parents_b[k].ResolveToAliveNode())
+            for k in (parents_a.keys() & parents_b.keys())
+        })
+
+        # Re-add the node back into the graph.
+        for child in children.values():
+            child = child.ResolveToAliveNode()
+            new_node.AddChild(child)
+        for parent in parents.values():
+            parent = parent.ResolveToAliveNode()
+            parent.AddChild(new_node)
+
+        node_a.dead = new_node
+        node_b.dead = new_node
+        new_node.AssertValidity()
+        return new_node
+
+    def MergeChildren(node_a: ActionNode, node_b: ActionNode) -> None:
+        assert not node_a.dead
+        assert not node_b.dead
+        assert node_a.action.name is not node_b.action.name
+        node_a.AssertValidity()
+        node_b.AssertValidity()
+
+        node_a_name = node_a.action.name
+        node_b_name = node_b.action.name
+
+        children_a = list(node_a.children.copy().values())
+        children_b = list(node_b.children.copy().values())
+
+        for child in children_a + children_b:
+            child = child.ResolveToAliveNode()
+            child.AssertValidity()
+            child_name = child.action.name
+            if (child_name in node_a.children
+                    and child_name in node_b.children):
+                MergeNodes(node_a.children[child_name],
+                           node_b.children[child_name])
+            elif child_name in node_a.children:
+                if (node_b_name in child.parents):
+                    node_b = MergeNodes(node_b, child.parents[node_b_name])
+                else:
+                    node_b.AddChild(child)
+            elif child_name in node_b.children:
+                if (node_a_name in child.parents):
+                    node_a = MergeNodes(node_a, child.parents[node_a_name])
+                else:
+                    node_a.AddChild(child)
+            else:
+                assert False
+            node_a = node_a.ResolveToAliveNode()
+            node_b = node_b.ResolveToAliveNode()
+
+        resolved_children = ({
+            k: node_a.children.get(k).ResolveToAliveNode()
+            for k in node_a.children.keys()
+        })
+        node_a.children = resolved_children.copy()
+        node_b.children = resolved_children
+
+        merged_state_check_coverage = MergeStateCheckCoverage(
+            node_a.state_check_actions_coverage,
+            node_b.state_check_actions_coverage)
+        node_a.state_check_actions.update(node_b.state_check_actions)
+        node_a.state_check_actions_coverage = merged_state_check_coverage
+        node_b.state_check_actions.update(node_a.state_check_actions)
+        node_b.state_check_actions_coverage = merged_state_check_coverage.copy(
+        )
+        node_a.AssertValidity()
+        node_b.AssertValidity()
+
+    def AddActionListToGraph(parent: ActionNode, current: ActionNode,
+                             action_list: List[Action], end: ActionNode,
+                             partial_tests: Set[CoverageTest]) -> None:
+        nonlocal root_node
+        assert isinstance(parent, ActionNode)
+        assert isinstance(end, ActionNode)
+        assert len(action_list) > 0
+        only_state_check_actions = True
+        current.AssertValidity()
+        end.AssertValidity()
+        for action in action_list:
+            assert isinstance(action, Action)
+            if action.is_state_check:
+                parent.AddStateCheckAction(action, ActionCoverage.PARTIAL)
+                parent.partial_coverage_tests.update(partial_tests)
+                continue
+            only_state_check_actions = False
+            if action.name in parent.children:
+                current = parent.children[action.name]
+            else:
+                current = ActionNode(action)
+                current.coverage = ActionCoverage.PARTIAL
+                parent.AddChild(current)
+            current.partial_coverage_tests.update(partial_tests)
+            parent = current
+        if current == end:
+            return
+        if only_state_check_actions:
+            # If only state_check actions were added, then no need to add a new
+            # edge.
+            return
+        logging.info("Merging children of\n{" + parent.GetGraphPathStr() +
+                     "}\n{" + end.GetGraphPathStr() + "}")
+        if current.action.name is end.action.name:
+            MergeNodes(current, end)
+        else:
+            MergeChildren(current, end)
+
+        root_node.AssertChildrenValidity()
+
+    def AddPartialPathsHelper(parent: ActionNode, current: ActionNode) -> None:
+        nonlocal partial_path_existence
+        nonlocal partial_paths
+        assert isinstance(current, ActionNode)
+        assert isinstance(parent, ActionNode)
+        assert current.action.name in parent.children
+        # Kick off all of the path replacement before actually doing the
+        # replacement, to prevent any unforeseen feedback issues.
+        for child in current.children.copy().values():
+            AddPartialPathsHelper(current, child)
+
+        if current.action.name not in partial_path_existence:
+            return
+        for partial_path in partial_paths:
+            if current.action.name != partial_path.input_actions[0].name:
+                continue
+            (end_node,
+             test_to_partial_cover) = FollowGraph(parent,
+                                                  partial_path.input_actions)
+            if end_node is None:
+                continue
+            if (logging.getLogger().isEnabledFor(logging.INFO)):
+                edge_str = " -> ".join(
+                    action.name for action in partial_path.output_actions)
+                logging.info(f"Inserting edge {{{edge_str}}} into graph at \n"
+                             f"{{{parent.GetGraphPathStr()}}}\n"
+                             f"{{{end_node.GetGraphPathStr()}}}")
+            AddActionListToGraph(parent, current, partial_path.output_actions,
+                                 end_node, test_to_partial_cover)
+
+    # Skip the root node, as it is only there for the algorithm to work.
+    for child in root_node.children.copy().values():
+        AddPartialPathsHelper(root_node, child)
+
+
+def GenerateGraphvizDotFile(root_node: ActionNode) -> str:
+    def GetAllActionNodesAndAssignGraphIds(root: ActionNode
+                                           ) -> List[ActionNode]:
+        current_graph_id = 0
+
+        def GetAllActionNodesHelper(node):
+            nonlocal current_graph_id
+            if node.graph_id is not None:
+                return []
+            node.graph_id = current_graph_id
+            current_graph_id += 1
+            nodes = [node]
+            if not node.children:
+                return nodes
+            for child in node.children.values():
+                nodes.extend(GetAllActionNodesHelper(child))
+            return nodes
+
+        # Skip the root node, as it is only there for the algorithm to work.
+        all_nodes = []
+        for child in root.children.values():
+            all_nodes.extend(GetAllActionNodesHelper(child))
+        return all_nodes
+
+    def PrintGraph(node: ActionNode) -> List[str]:
+        assert node.graph_id is not None, node.action.name
+        if not node.children:
+            return []
+        lines = []
+        for child in node.children.values():
+            assert child.graph_id is not None, child.action.name
+            edge_str = str(node.graph_id) + " -> " + str(child.graph_id)
+            lines.append(edge_str)
+            lines.extend(PrintGraph(child))
+        return lines
+
+    lines = []
+    lines.append("strict digraph {")
+    nodes = GetAllActionNodesAndAssignGraphIds(root_node)
+    for node in nodes:
+        color_str = ("seagreen"
+                     if node.coverage == ActionCoverage.FULL else "sandybrown")
+        label_str = node.GetGraphvizLabel()
+        lines.append(f"{node.graph_id}[label={label_str} color={color_str}]")
+    # Skip the root node, as it is only there for the algorithm to work.
+    for child in root_node.children.values():
+        lines.extend(PrintGraph(child))
+    lines.append("}")
+    return "\n".join(lines)
+
+
+# Removes any nodes from the graph that are not in the actions dictionary
+# given. This completely messes up the the |parents| field, so do not rely
+# on that after using this function.
+def TrimGraphToAllowedActions(root_node: ActionNode,
+                              allowed_actions: Dict[str, Action]) -> None:
+    new_children = {}
+    for child in root_node.children.values():
+        if child.action.name in allowed_actions:
+            new_children[child.action.name] = child
+    root_node.children = new_children
+    new_state_check_actions = {}
+    for action in root_node.state_check_actions.values():
+        if action.name in allowed_actions:
+            new_state_check_actions[action.name] = action
+    root_node.state_check_actions = new_state_check_actions
+    for child in root_node.children.values():
+        TrimGraphToAllowedActions(child, allowed_actions)
+
+
+def GenerateFrameworkTests(root_node: ActionNode, tests: List[CoverageTest]
+                           ) -> List[List[ActionNode]]:
+    assert isinstance(root_node, ActionNode)
+
+    def GetAllPaths(node: ActionNode) -> List[List[ActionNode]]:
+        assert node is not None
+        assert isinstance(node, ActionNode)
+        paths = []
+        for child in node.children.values():
+            for path in GetAllPaths(child):
+                assert path is not None
+                assert isinstance(path, list)
+                assert bool(path)
+                paths.append([node] + path)
+        if len(paths) == 0:
+            paths = [[node]]
+        return paths
+
+    def FindLongestFullCoveragePath(paths: List[List[ActionNode]],
+                                    test: CoverageTest):
+        best = None
+        best_length = 0
+        for path in paths:
+            assert isinstance(path, list)
+            len = 0
+            for node in path:
+                if test in node.full_coverage_tests:
+                    len += 100
+                elif test in node.partial_coverage_tests:
+                    len += 1
+            if len > best_length:
+                best_length = len
+                best = path
+        return best
+
+    def PruneNonKeepNodes(node: ActionNode):
+        children = {}
+        for child in node.children.values():
+            if child.keep:
+                children[child.action.name] = child
+                PruneNonKeepNodes(child)
+        node.children = children
+        return node
+
+    if len(tests) == 0:
+        return []
+
+    all_paths = GetAllPaths(root_node)
+
+    root_node.keep = True
+    for test in tests:
+        best = FindLongestFullCoveragePath(all_paths, test)
+        if best is None:
+            continue
+        if (logging.getLogger().isEnabledFor(logging.INFO)):
+            path_str = ", ".join([node.action.name for node in best])
+            logging.info(f"Best path for test {test.name}:\n{path_str}")
+        for node in best:
+            node.keep = True
+    PruneNonKeepNodes(root_node)
+    return GetAllPaths(root_node)
+
+
+def GenerateCoverageFileAndPercents(
+        required_coverage_tests: List[CoverageTest],
+        tested_graph_root: ActionNode) -> Tuple[str, float, float]:
+    lines = []
+    total_actions = 0.0
+    actions_fully_covered = 0.0
+    actions_partially_covered = 0.0
+    for coverage_test in required_coverage_tests:
+        action_strings = []
+        last_action_node = tested_graph_root
+        for action in coverage_test.actions:
+            total_actions += 1
+            coverage = None
+            if last_action_node is not None:
+                if action.name in last_action_node.children:
+                    coverage = last_action_node.children[action.name].coverage
+                if (action.is_state_check
+                        and last_action_node.HasStateCheckAction(action)):
+                    coverage = last_action_node.state_check_actions_coverage[
+                        action.name]
+            if coverage is None:
+                last_action_node = None
+                action_strings.append(action.name + '🌑')
+                continue
+            elif (coverage == ActionCoverage.FULL):
+                actions_fully_covered += 1
+                action_strings.append(action.name + '🌕')
+            elif (coverage == ActionCoverage.PARTIAL):
+                action_strings.append(action.name + '🌓')
+                actions_partially_covered += 1
+            # Only proceed if the action was in the children. If not, then it
+            # was in the stateless action list and we stay at the same node.
+            if action.name in last_action_node.children:
+                last_action_node = last_action_node.children[action.name]
+        lines.append(action_strings)
+
+    full_coverage = actions_fully_covered / total_actions
+    partial_coverage = ((actions_fully_covered + actions_partially_covered) /
+                        total_actions)
+    logging.info("Coverage:")
+    logging.info(f"Full coverage: {full_coverage:.2f}, "
+                 f"Partial coverage: {partial_coverage:.2f}")
+    return ("\n".join(["\t".join(line)
+                       for line in lines]), full_coverage, partial_coverage)
diff --git a/chrome/test/webapps/graph_cli_tool.py b/chrome/test/webapps/graph_cli_tool.py
new file mode 100755
index 0000000..0dfb996
--- /dev/null
+++ b/chrome/test/webapps/graph_cli_tool.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Command line tool to analyze and debug the action graph algorithms.
+"""
+
+import argparse
+import csv
+import logging
+from typing import List, Set, Tuple
+import os
+
+from classes import Action
+from classes import ActionNode
+from classes import CoverageTest
+from file_reading import ReadActionsFile
+from file_reading import ReadCoverageTestsFile
+from file_reading import ReadFrameworkActions
+from file_reading import ReadPartialCoveragePathsFile
+from file_reading import ReadNamedTestsFile
+from graph_analysis import CreateFullCoverageActionGraph
+from graph_analysis import AddPartialPaths
+from graph_analysis import GenerateCoverageFileAndPercents
+from graph_analysis import GenerateFrameworkTests
+from graph_analysis import GenerateGraphvizDotFile
+from graph_analysis import TrimGraphToAllowedActions
+
+
+def MaybeFilterCoverageTests(coverage_tests: List[CoverageTest],
+                             filter: List[str]) -> List[CoverageTest]:
+    if not filter:
+        return coverage_tests
+    return [coverage_tests[int(index_as_string)] for index_as_string in filter]
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='WebApp Integration Test Analysis CLI Tool')
+    parser.add_argument('-v',
+                        dest='v',
+                        action='store_true',
+                        help='Include info logging.',
+                        required=False)
+
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+
+    parser.add_argument(
+        '--test_dir',
+        dest='test_dir',
+        action='store',
+        help=('Specify a directory to find all required files, instead of ' +
+              'specifying each file individually. Overrides those options.'),
+        required=False)
+
+    parser.add_argument(
+        '--coverage_required',
+        dest='coverage_required',
+        action='store',
+        default=(script_dir + '/data/coverage_required.csv'),
+        help=(
+            'Test list csv file, which lists all integration tests that would '
+            + 'give the required full coverage of the system. The first two ' +
+            'lines are skipped.'),
+        required=False)
+    parser.add_argument('--coverage_test_row',
+                        dest='coverage_test_row',
+                        action='append',
+                        help='Individually select a coverage test row.',
+                        required=False)
+
+    parser.add_argument(
+        '--partial_coverage_paths',
+        dest='partial_coverage_paths',
+        action='store',
+        default=(script_dir + '/data/partial_coverage_paths.csv'),
+        help=('File with path replacement descriptions to create partial ' +
+              'coverage paths in the tree.'),
+        required=False)
+
+    parser.add_argument('--actions',
+                        dest='actions',
+                        action='store',
+                        default=(script_dir + '/data/actions.csv'),
+                        help='Actions csv file, defining all actions.',
+                        required=False)
+
+    parser.add_argument('--framework_actions',
+                        dest='framework_actions',
+                        default=(script_dir +
+                                 '/data/framework_actions_linux.csv'),
+                        help=('Framework actions csv file, enumerating ' +
+                              'all actions supported by the framework'),
+                        action='store',
+                        required=False)
+    parser.add_argument(
+        '--tests',
+        dest='tests',
+        action='append',
+        help=(
+            'Test csv files, enumerating all existing tests for coverage ' +
+            'calculations. First line is skipped, and first column is skipped.'
+        ),
+        required=False)
+
+    subparsers = parser.add_subparsers(dest="cmd", required=True)
+    subparsers.add_parser('list_actions')
+    subparsers.add_parser('list_coverage_tests')
+    subparsers.add_parser('list_tests')
+    subparsers.add_parser('list_partial_paths')
+    subparsers.add_parser('coverage_required_graph')
+
+    framework_parse = subparsers.add_parser('generate_framework_tests')
+    framework_parse.add_argument('--graph_framework_tests',
+                                 dest='graph_framework_tests',
+                                 default=False,
+                                 action='store_true')
+
+    coverage_parse = subparsers.add_parser('generate_test_coverage')
+    coverage_parse.add_argument('--graph_test_coverage',
+                                dest='graph_test_coverage',
+                                default=False,
+                                action='store_true')
+
+    options = parser.parse_args()
+
+    actions_file = options.actions
+    coverage_required_file = options.coverage_required
+    partial_paths_file = options.partial_coverage_paths
+    framework_actions_file = options.framework_actions
+
+    if options.test_dir:
+        actions_file = options.test_dir + "/actions.csv"
+        coverage_required_file = options.test_dir + "/coverage_required.csv"
+        partial_paths_file = options.test_dir + "/partial_coverage_paths.csv"
+        framework_actions_file = (options.test_dir +
+                                  "/framework_actions_linux.csv")
+
+    logging.basicConfig(level=logging.INFO if options.v else logging.WARN,
+                        format='[%(asctime)s %(levelname)s] %(message)s',
+                        datefmt='%H:%M:%S')
+
+    logging.info('Script directory: ' + script_dir)
+
+    actions_csv = csv.reader(open(actions_file), delimiter=',')
+    (actions, action_base_name_to_default_param,
+     action_base_name_to_all_params) = ReadActionsFile(actions_csv)
+
+    if options.cmd == 'list_actions':
+        for action in actions.values():
+            print(action)
+        return
+    if options.cmd == 'list_tests':
+        tests = []
+        for tests_file in options.tests:
+            tests_csv = csv.reader(open(tests_file), delimiter=',')
+            tests.extend(
+                ReadNamedTestsFile(tests_csv, actions,
+                                   action_base_name_to_default_param))
+        for test in tests:
+            print(test)
+        return
+    if options.cmd == 'list_coverage_tests':
+        coverage_csv = csv.reader(open(coverage_required_file), delimiter=',')
+        required_coverage_tests = ReadCoverageTestsFile(
+            coverage_csv, actions, action_base_name_to_default_param)
+        required_coverage_tests = MaybeFilterCoverageTests(
+            required_coverage_tests, options.coverage_test_row)
+        for test in required_coverage_tests:
+            print(test)
+        return
+    if options.cmd == 'list_partial_paths':
+        partial_csv = csv.reader(open(partial_paths_file), delimiter=',')
+        partial_paths = ReadPartialCoveragePathsFile(
+            partial_csv, actions, action_base_name_to_all_params)
+        for partial_path in partial_paths:
+            print(partial_path)
+        return
+    if options.cmd == 'coverage_required_graph':
+        coverage_csv = csv.reader(open(coverage_required_file), delimiter=',')
+        required_coverage_tests = ReadCoverageTestsFile(
+            coverage_csv, actions, action_base_name_to_default_param)
+        required_coverage_tests = MaybeFilterCoverageTests(
+            required_coverage_tests, options.coverage_test_row)
+        coverage_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(coverage_root_node,
+                                      required_coverage_tests)
+        partial_csv = csv.reader(open(partial_paths_file), delimiter=',')
+        partial_paths = ReadPartialCoveragePathsFile(
+            partial_csv, actions, action_base_name_to_all_params)
+        AddPartialPaths(coverage_root_node, partial_paths)
+        graph_file = GenerateGraphvizDotFile(coverage_root_node)
+        print(graph_file)
+        return
+    if options.cmd == 'generate_framework_tests':
+        coverage_csv = csv.reader(open(coverage_required_file), delimiter=',')
+        framework_actions_csv = csv.reader(open(framework_actions_file),
+                                           delimiter=',')
+        required_coverage_tests = ReadCoverageTestsFile(
+            coverage_csv, actions, action_base_name_to_default_param)
+        required_coverage_tests = MaybeFilterCoverageTests(
+            required_coverage_tests, options.coverage_test_row)
+        framework_actions = ReadFrameworkActions(
+            framework_actions_csv, actions, action_base_name_to_default_param)
+        coverage_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(coverage_root_node,
+                                      required_coverage_tests)
+        partial_csv = csv.reader(open(partial_paths_file), delimiter=',')
+        partial_paths = ReadPartialCoveragePathsFile(
+            partial_csv, actions, action_base_name_to_all_params)
+        AddPartialPaths(coverage_root_node, partial_paths)
+
+        TrimGraphToAllowedActions(coverage_root_node, framework_actions)
+        if options.graph_framework_tests:
+            return GenerateGraphvizDotFile(coverage_root_node)
+
+        lines = ["# This is a generated file."]
+        paths = GenerateFrameworkTests(coverage_root_node,
+                                       required_coverage_tests)
+        for path in paths:
+            all_actions_in_path = []
+            for node in path[1:]:  # Skip the root node
+                all_actions_in_path.append(node.action)
+                all_actions_in_path.extend(node.state_check_actions.values())
+            lines.append(
+                "," + ",".join([action.name
+                                for action in all_actions_in_path]))
+        print("\n".join(lines))
+        return
+    if options.cmd == 'generate_test_coverage':
+        coverage_csv = csv.reader(open(coverage_required_file), delimiter=',')
+        required_coverage_tests = ReadCoverageTestsFile(
+            coverage_csv, actions, action_base_name_to_default_param)
+        required_coverage_tests = MaybeFilterCoverageTests(
+            required_coverage_tests, options.coverage_test_row)
+        partial_csv = csv.reader(open(partial_paths_file), delimiter=',')
+        partial_paths = ReadPartialCoveragePathsFile(
+            partial_csv, actions, action_base_name_to_all_params)
+        for path in partial_paths:
+            path.Reverse()
+        tests = []
+        for tests_file in options.tests:
+            tests_csv = csv.reader(open(tests_file), delimiter=',')
+            tests.extend(
+                ReadNamedTestsFile(tests_csv, actions,
+                                   action_base_name_to_default_param))
+        tests_root_node = ActionNode(Action("root", "root", False))
+        CreateFullCoverageActionGraph(tests_root_node, tests)
+        AddPartialPaths(tests_root_node, partial_paths)
+
+        if options.graph_test_coverage:
+            print(GenerateGraphvizDotFile(tests_root_node))
+            return
+        (coverage_file, _,
+         _) = GenerateCoverageFileAndPercents(required_coverage_tests,
+                                              tests_root_node)
+        print(coverage_file)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/chrome/test/webapps/output/coverage_cros.tsv b/chrome/test/webapps/output/coverage_cros.tsv
new file mode 100644
index 0000000..af9d8ae
--- /dev/null
+++ b/chrome/test/webapps/output/coverage_cros.tsv
@@ -0,0 +1,210 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+# Full Coverage: 0.50, Partial Coverage: 0.62
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	delete_profile🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	remove_policy_app_site_a🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	delete_profile🌓	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	remove_policy_app_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	assert_platform_shortcut_exists_site_a🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	clear_app_badge_site_a🌑	assert_app_badge_empty_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	list_apps🌑	assert_app_list_empty🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	remove_policy_app_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_create_shortcut_windowed🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	delete_profile🌑	user_signin🌑	list_apps🌑	install_locally_site_a🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_created_site_a🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_right_click_menu_has_actions_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	launch_from_list_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a_foo🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_list_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_toolbar🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	list_apps🌓	launch_from_list_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_b🌓	launch_from_omnibox_or_menu🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_correct_window_title🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+assert_correct_window_title🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	launch_from_platform_shortcut_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_correct_window_title🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	assert_window_display_minimal🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_colors_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_color_correct🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_browser_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_minimal_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌕	assert_platform_shortcut_icon_correct_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	list_apps🌑	assert_app_in_list_icon_correct_site_a🌑
+navigate_installable_site_a_foo🌓	install_omnibox_or_menu🌓	manifest_update_scope_site_a_foo_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a_foo🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_bar🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_in_scope_site_a_foo🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_out_scope_site_a🌑	assert_launch_icon_not_shown🌑	navigate_browser_in_scope_site_a🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_installable_site_a_bar🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_foo🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_out_scope_site_a_bar🌑	assert_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_crashed_url🌕	assert_create_shortcut_not_shown🌕
+navigate_installable_site_a🌕	assert_create_shortcut_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_window_created🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_not_installable_site_c🌕	assert_create_shortcut_shown🌕
+navigate_not_installable_site_c🌕	assert_install_icon_not_shown🌕
+navigate_notfound_url🌕	assert_create_shortcut_not_shown🌕
+navigate_notfound_url🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	close_custom_toolbar🌑	assert_navigation_start_url🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	assert_app_badge_has_value_site_a🌑
+navigate_installable_site_a🌕	set_app_badge_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	install_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_create_shortcut_not_shown🌑
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_incognito_profile🌕	navigate_installable_site_a🌑	assert_launch_icon_not_shown🌑
+switch_incognito_profile🌕	navigate_not_installable_site_c🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	sync_turn_off🌕	install_omnibox_or_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	sync_turn_off🌕	install_create_shortcut_tabbed🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	launch_from_platform_shortcut_site_a🌓	uninstall_from_menu🌓	sync_turn_on🌕	switch_profile_clients_user_a_client_1🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	list_apps🌓	uninstall_from_app_list_site_a🌓	sync_turn_on🌕	switch_profile_clients_user_a_client_1🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update_with_bmo_migration🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	launch_from_list_site_a🌓	assert_tab_created🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑	assert_launch_icon_shown🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	uninstall_from_app_list_site_a🌑	assert_app_not_in_list_site_a🌑
\ No newline at end of file
diff --git a/chrome/test/webapps/output/coverage_linux.tsv b/chrome/test/webapps/output/coverage_linux.tsv
new file mode 100644
index 0000000..625a71a
--- /dev/null
+++ b/chrome/test/webapps/output/coverage_linux.tsv
@@ -0,0 +1,210 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+# Full Coverage: 0.51, Partial Coverage: 0.64
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	delete_profile🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	remove_policy_app_site_a🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	delete_profile🌓	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	remove_policy_app_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	assert_platform_shortcut_exists_site_a🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	clear_app_badge_site_a🌑	assert_app_badge_empty_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	list_apps🌑	assert_app_list_empty🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	remove_policy_app_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_create_shortcut_windowed🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	delete_profile🌑	user_signin🌑	list_apps🌑	install_locally_site_a🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_created_site_a🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_right_click_menu_has_actions_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	launch_from_list_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a_foo🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_list_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_toolbar🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	list_apps🌓	launch_from_list_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_b🌓	launch_from_omnibox_or_menu🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_correct_window_title🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+assert_correct_window_title🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	launch_from_platform_shortcut_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_correct_window_title🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	assert_window_display_minimal🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_colors_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_color_correct🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_browser_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_minimal_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌕	assert_platform_shortcut_icon_correct_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	list_apps🌑	assert_app_in_list_icon_correct_site_a🌑
+navigate_installable_site_a_foo🌓	install_omnibox_or_menu🌓	manifest_update_scope_site_a_foo_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a_foo🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_bar🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_in_scope_site_a_foo🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_out_scope_site_a🌑	assert_launch_icon_not_shown🌑	navigate_browser_in_scope_site_a🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_installable_site_a_bar🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_foo🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_out_scope_site_a_bar🌑	assert_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_crashed_url🌕	assert_create_shortcut_not_shown🌕
+navigate_installable_site_a🌕	assert_create_shortcut_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_window_created🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_not_installable_site_c🌕	assert_create_shortcut_shown🌕
+navigate_not_installable_site_c🌕	assert_install_icon_not_shown🌕
+navigate_notfound_url🌕	assert_create_shortcut_not_shown🌕
+navigate_notfound_url🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	close_custom_toolbar🌑	assert_navigation_start_url🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	assert_app_badge_has_value_site_a🌑
+navigate_installable_site_a🌕	set_app_badge_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	install_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_create_shortcut_not_shown🌑
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_incognito_profile🌕	navigate_installable_site_a🌑	assert_launch_icon_not_shown🌑
+switch_incognito_profile🌕	navigate_not_installable_site_c🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	sync_turn_off🌕	install_omnibox_or_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	sync_turn_off🌕	install_create_shortcut_tabbed🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	launch_from_platform_shortcut_site_a🌓	uninstall_from_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_1🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	list_apps🌓	uninstall_from_app_list_site_a🌑	sync_turn_on🌑	switch_profile_clients_user_a_client_1🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update_with_bmo_migration🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	launch_from_list_site_a🌑	assert_tab_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕	assert_launch_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	uninstall_from_app_list_site_a🌑	assert_app_not_in_list_site_a🌑
\ No newline at end of file
diff --git a/chrome/test/webapps/output/coverage_mac.tsv b/chrome/test/webapps/output/coverage_mac.tsv
new file mode 100644
index 0000000..625a71a
--- /dev/null
+++ b/chrome/test/webapps/output/coverage_mac.tsv
@@ -0,0 +1,210 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+# Full Coverage: 0.51, Partial Coverage: 0.64
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	delete_profile🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	remove_policy_app_site_a🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	delete_profile🌓	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	remove_policy_app_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	assert_platform_shortcut_exists_site_a🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	clear_app_badge_site_a🌑	assert_app_badge_empty_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	list_apps🌑	assert_app_list_empty🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	remove_policy_app_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_create_shortcut_windowed🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	delete_profile🌑	user_signin🌑	list_apps🌑	install_locally_site_a🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_created_site_a🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_right_click_menu_has_actions_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	launch_from_list_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a_foo🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_list_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_toolbar🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	list_apps🌓	launch_from_list_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_b🌓	launch_from_omnibox_or_menu🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_correct_window_title🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+assert_correct_window_title🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	launch_from_platform_shortcut_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_correct_window_title🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	assert_window_display_minimal🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_colors_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_color_correct🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_browser_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_minimal_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌕	assert_platform_shortcut_icon_correct_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	list_apps🌑	assert_app_in_list_icon_correct_site_a🌑
+navigate_installable_site_a_foo🌓	install_omnibox_or_menu🌓	manifest_update_scope_site_a_foo_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a_foo🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_bar🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_in_scope_site_a_foo🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_out_scope_site_a🌑	assert_launch_icon_not_shown🌑	navigate_browser_in_scope_site_a🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_installable_site_a_bar🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_foo🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_out_scope_site_a_bar🌑	assert_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_crashed_url🌕	assert_create_shortcut_not_shown🌕
+navigate_installable_site_a🌕	assert_create_shortcut_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_window_created🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_not_installable_site_c🌕	assert_create_shortcut_shown🌕
+navigate_not_installable_site_c🌕	assert_install_icon_not_shown🌕
+navigate_notfound_url🌕	assert_create_shortcut_not_shown🌕
+navigate_notfound_url🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	close_custom_toolbar🌑	assert_navigation_start_url🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	assert_app_badge_has_value_site_a🌑
+navigate_installable_site_a🌕	set_app_badge_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	install_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_create_shortcut_not_shown🌑
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_incognito_profile🌕	navigate_installable_site_a🌑	assert_launch_icon_not_shown🌑
+switch_incognito_profile🌕	navigate_not_installable_site_c🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	sync_turn_off🌕	install_omnibox_or_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	sync_turn_off🌕	install_create_shortcut_tabbed🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	launch_from_platform_shortcut_site_a🌓	uninstall_from_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_1🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	list_apps🌓	uninstall_from_app_list_site_a🌑	sync_turn_on🌑	switch_profile_clients_user_a_client_1🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update_with_bmo_migration🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	launch_from_list_site_a🌑	assert_tab_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕	assert_launch_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	uninstall_from_app_list_site_a🌑	assert_app_not_in_list_site_a🌑
\ No newline at end of file
diff --git a/chrome/test/webapps/output/coverage_win.tsv b/chrome/test/webapps/output/coverage_win.tsv
new file mode 100644
index 0000000..625a71a
--- /dev/null
+++ b/chrome/test/webapps/output/coverage_win.tsv
@@ -0,0 +1,210 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+# Full Coverage: 0.51, Partial Coverage: 0.64
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	delete_profile🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌓	remove_policy_app_site_a🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_shown🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	delete_profile🌓	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌕	remove_policy_app_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_create_shortcut_not_shown🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	assert_platform_shortcut_exists_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	delete_profile🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	assert_platform_shortcut_exists_site_a🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	navigate_browser_in_scope_site_a🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	navigate_browser_in_scope_site_a🌑	assert_install_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	launch_from_platform_shortcut_site_a🌑	uninstall_from_menu🌑	assert_window_closed🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	clear_app_badge_site_a🌑	assert_app_badge_empty_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_platform_shortcut🌑	create_shortcuts🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	list_apps🌑	assert_app_list_empty🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_windowed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_not_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	remove_policy_app_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_windowed🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_create_shortcut_windowed🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_platform_shortcut_created_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_window_created🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	chrome_update_with_bmo_migration🌑	launch_from_platform_shortcut_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+launch_from_omnibox_or_menu🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	delete_profile🌑	user_signin🌑	list_apps🌑	install_locally_site_a🌑	navigate_browser_in_scope_site_a🌑	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_tabbed_no_shortcut_site_a🌑	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_created_site_a🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	assert_platform_shortcut_right_click_menu_has_actions_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	chrome_update_with_bmo_migration🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	launch_from_list_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a_foo🌑	assert_install_icon_not_shown🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	assert_platform_shortcut_exists_site_a🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_list_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_toolbar🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	list_apps🌓	launch_from_list_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_omnibox_or_menu🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_b🌓	launch_from_omnibox_or_menu🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_correct_window_title🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	close_pwa🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_a🌕	navigate_pwa_out_scope_site_a🌕	assert_toolbar🌕
+assert_correct_window_title🌑	navigate_installable_site_a🌑	install_omnibox_or_menu🌑	launch_from_platform_shortcut_site_a🌑	navigate_pwa_out_scope_site_a🌑	assert_correct_window_title🌑
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	assert_window_display_minimal🌕
+navigate_installable_site_b🌕	install_omnibox_or_menu🌕	launch_from_platform_shortcut_site_b🌓	use_pwa_window_controls🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_colors_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_color_correct🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_browser_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_standalone🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_display_minimal_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌑	assert_window_display_minimal🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	launch_from_platform_shortcut_site_a🌕	assert_platform_shortcut_icon_correct_site_a🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_icons_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a🌓	close_pwa🌓	list_apps🌑	assert_app_in_list_icon_correct_site_a🌑
+navigate_installable_site_a_foo🌓	install_omnibox_or_menu🌓	manifest_update_scope_site_a_foo_site_a🌕	close_pwa🌓	launch_from_platform_shortcut_site_a_foo🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_bar🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_in_scope_site_a_foo🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_browser_out_scope_site_a🌑	assert_launch_icon_not_shown🌑	navigate_browser_in_scope_site_a🌑	assert_launch_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_installable_site_a_bar🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_in_scope_site_a_foo🌑	assert_no_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	manifest_update_scope_site_a_site_a_foo🌑	close_pwa🌑	launch_from_platform_shortcut_site_a🌑	close_pwa🌑	navigate_pwa_out_scope_site_a_bar🌑	assert_toolbar🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_in_scope_site_a🌕	launch_from_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_install_icon_shown🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_browser_out_scope_site_b🌑	assert_launch_icon_not_shown🌑
+navigate_crashed_url🌕	assert_create_shortcut_not_shown🌕
+navigate_installable_site_a🌕	assert_create_shortcut_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	assert_window_created🌕	list_apps🌕	assert_app_in_list_windowed_site_a🌕
+navigate_not_installable_site_c🌕	assert_create_shortcut_shown🌕
+navigate_not_installable_site_c🌕	assert_install_icon_not_shown🌕
+navigate_notfound_url🌕	assert_create_shortcut_not_shown🌕
+navigate_notfound_url🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_in_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	close_custom_toolbar🌑	assert_navigation_start_url🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	open_in_chrome🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	navigate_pwa_out_scope_site_a🌑	uninstall_from_menu🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+user_signin🌕	add_policy_app_windowed_shortcut_site_a🌕	remove_policy_app_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕
+user_signin🌕	add_policy_app_tabbed_shortcut_site_a🌕	remove_policy_app_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	set_app_badge_site_a🌑	assert_app_badge_has_value_site_a🌑
+navigate_installable_site_a🌕	set_app_badge_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_tab_created🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	install_omnibox_or_menu🌕	assert_window_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_tab_created🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	set_open_in_tab_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	launch_from_list_site_a🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_omnibox_or_menu🌑	assert_window_created🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	launch_from_platform_shortcut_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	assert_app_in_list_windowed_site_a🌑
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	list_apps🌓	launch_from_list_site_a🌓	assert_window_created🌕
+navigate_installable_site_a🌕	install_create_shortcut_tabbed🌕	list_apps🌓	set_open_in_window_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_launch_icon_shown🌕
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_create_shortcut_not_shown🌑
+switch_incognito_profile🌕	navigate_installable_site_a🌕	assert_install_icon_not_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_incognito_profile🌕	navigate_installable_site_a🌑	assert_launch_icon_not_shown🌑
+switch_incognito_profile🌕	navigate_not_installable_site_c🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	sync_turn_off🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	sync_turn_off🌕	install_omnibox_or_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	sync_turn_off🌕	install_create_shortcut_tabbed🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑	install_locally_site_a🌑	assert_app_in_list_not_windowed_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	launch_from_platform_shortcut_site_a🌓	uninstall_from_menu🌕	sync_turn_on🌕	switch_profile_clients_user_a_client_1🌕	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	user_signin🌕	switch_profile_clients_user_a_client_2🌕	sync_turn_off🌕	list_apps🌓	uninstall_from_app_list_site_a🌑	sync_turn_on🌑	switch_profile_clients_user_a_client_1🌑	assert_platform_shortcut_not_exists_site_a🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	list_apps🌕	uninstall_from_app_list_site_a🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_not_in_list_site_a🌑	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	list_apps🌓	assert_app_not_in_list_site_a🌕	assert_platform_shortcut_not_exists_site_a🌑
+navigate_installable_site_a🌕	install_omnibox_or_menu🌕	uninstall_from_menu🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	chrome_update_with_bmo_migration🌑	list_apps🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	chrome_update_with_bmo_migration🌑	list_apps🌑	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	install_locally_site_a🌓	assert_app_in_list_windowed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	install_locally_site_a🌓	assert_app_in_list_not_windowed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	launch_from_list_site_a🌑	assert_tab_created🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	list_apps🌕	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_not_installable_site_c🌕	install_create_shortcut_tabbed🌕	delete_profile🌓	user_signin🌓	list_apps🌓	assert_app_in_list_not_locally_installed_site_a🌑
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	install_locally_site_a🌓	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕	assert_launch_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	delete_profile🌕	user_signin🌕	navigate_browser_in_scope_site_a🌕	assert_install_icon_shown🌕
+user_signin🌕	navigate_installable_site_a🌕	install_omnibox_or_menu🌕	switch_profile_clients_user_a_client_2🌕	list_apps🌕	uninstall_from_app_list_site_a🌑	assert_app_not_in_list_site_a🌑
\ No newline at end of file
diff --git a/chrome/test/webapps/output/framework_tests_cros.csv b/chrome/test/webapps/output/framework_tests_cros.csv
new file mode 100644
index 0000000..7c731a4
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_cros.csv
@@ -0,0 +1,22 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown,assert_launch_icon_not_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,list_apps_internal
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_not_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_omnibox_or_menu,assert_user_display_mode_standalone_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,add_policy_app_windowed_shortcut_site_a,remove_policy_app_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created,close_pwa
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created,install_omnibox_or_menu,assert_window_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_create_shortcut_tabbed,user_signin
+,navigate_installable_site_a,install_create_shortcut_tabbed,list_apps_internal
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_linux.csv b/chrome/test/webapps/output/framework_tests_linux.csv
new file mode 100644
index 0000000..6033d08
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_linux.csv
@@ -0,0 +1,24 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown,assert_launch_icon_not_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,list_apps_internal
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_not_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_omnibox_or_menu,assert_user_display_mode_standalone_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,add_policy_app_windowed_shortcut_site_a,remove_policy_app_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created,close_pwa
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created,install_omnibox_or_menu,assert_window_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_create_shortcut_tabbed,user_signin
+,navigate_installable_site_a,install_create_shortcut_tabbed,list_apps_internal
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_mac.csv b/chrome/test/webapps/output/framework_tests_mac.csv
new file mode 100644
index 0000000..6033d08
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_mac.csv
@@ -0,0 +1,24 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown,assert_launch_icon_not_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,list_apps_internal
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_not_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_omnibox_or_menu,assert_user_display_mode_standalone_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,add_policy_app_windowed_shortcut_site_a,remove_policy_app_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created,close_pwa
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created,install_omnibox_or_menu,assert_window_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_create_shortcut_tabbed,user_signin
+,navigate_installable_site_a,install_create_shortcut_tabbed,list_apps_internal
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_sync_cros.csv b/chrome/test/webapps/output/framework_tests_sync_cros.csv
new file mode 100644
index 0000000..c6e0f60
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_sync_cros.csv
@@ -0,0 +1,15 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,sync_turn_off
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,launch_internal_site_a,assert_tab_created
+,user_signin,navigate_installable_site_a,sync_turn_off,install_omnibox_or_menu,sync_turn_on,switch_profile_clients_user_a_client_2,list_apps_internal
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,sync_turn_off
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,switch_profile_clients_user_a_client_2,list_apps_internal
+,user_signin,navigate_not_installable_site_c,sync_turn_off,install_create_shortcut_tabbed,sync_turn_on,switch_profile_clients_user_a_client_2,list_apps_internal
+,user_signin,install_internal_windowed_site_a,sync_turn_off
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,launch_internal_site_a,assert_tab_created
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,switch_profile_clients_user_a_client_2,sync_turn_off,uninstall_internal_site_a,sync_turn_on,switch_profile_clients_user_a_client_1,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal
diff --git a/chrome/test/webapps/output/framework_tests_sync_linux.csv b/chrome/test/webapps/output/framework_tests_sync_linux.csv
new file mode 100644
index 0000000..07b4411
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_sync_linux.csv
@@ -0,0 +1,15 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,sync_turn_off
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,user_signin,navigate_installable_site_a,sync_turn_off,install_omnibox_or_menu,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,sync_turn_off
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,sync_turn_off,install_create_shortcut_tabbed,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,install_internal_windowed_site_a,sync_turn_off
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,switch_profile_clients_user_a_client_2,sync_turn_off,launch_internal_site_a,uninstall_from_menu,sync_turn_on,switch_profile_clients_user_a_client_1,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_sync_mac.csv b/chrome/test/webapps/output/framework_tests_sync_mac.csv
new file mode 100644
index 0000000..07b4411
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_sync_mac.csv
@@ -0,0 +1,15 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,sync_turn_off
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,user_signin,navigate_installable_site_a,sync_turn_off,install_omnibox_or_menu,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,sync_turn_off
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,sync_turn_off,install_create_shortcut_tabbed,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,install_internal_windowed_site_a,sync_turn_off
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,switch_profile_clients_user_a_client_2,sync_turn_off,launch_internal_site_a,uninstall_from_menu,sync_turn_on,switch_profile_clients_user_a_client_1,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_sync_win.csv b/chrome/test/webapps/output/framework_tests_sync_win.csv
new file mode 100644
index 0000000..07b4411
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_sync_win.csv
@@ -0,0 +1,15 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,sync_turn_off
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,navigate_installable_site_a,install_omnibox_or_menu,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,user_signin,navigate_installable_site_a,sync_turn_off,install_omnibox_or_menu,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,sync_turn_off
+,user_signin,navigate_not_installable_site_c,install_create_shortcut_tabbed,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,navigate_not_installable_site_c,sync_turn_off,install_create_shortcut_tabbed,sync_turn_on,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a
+,user_signin,install_internal_windowed_site_a,sync_turn_off
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,install_internal_windowed_site_a,switch_profile_clients_user_a_client_2,assert_app_not_locally_installed_internal_site_a,list_apps_internal,install_locally_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown,assert_launch_icon_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,switch_profile_clients_user_a_client_2,sync_turn_off,launch_internal_site_a,uninstall_from_menu,sync_turn_on,switch_profile_clients_user_a_client_1,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,switch_profile_clients_user_a_client_2,list_apps_internal,install_locally_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/output/framework_tests_win.csv b/chrome/test/webapps/output/framework_tests_win.csv
new file mode 100644
index 0000000..6033d08
--- /dev/null
+++ b/chrome/test/webapps/output/framework_tests_win.csv
@@ -0,0 +1,24 @@
+# DO NOT EDIT - THIS IS A GENERATED FILE.
+# See /chrome/test/web_app/README.md for more info.
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown,assert_launch_icon_not_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_tabbed_shortcut_site_a,assert_user_display_mode_browser_internal_site_a,list_apps_internal
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,remove_policy_app_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,user_signin,add_policy_app_windowed_shortcut_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_create_shortcut_tabbed,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_not_shown
+,navigate_not_installable_site_c,assert_install_icon_not_shown,install_omnibox_or_menu,assert_user_display_mode_standalone_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,user_signin,add_policy_app_windowed_shortcut_site_a,remove_policy_app_site_a,assert_user_display_mode_standalone_internal_site_a,list_apps_internal
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_not_shown,assert_launch_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_from_menu,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created,close_pwa
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,set_open_in_tab_internal_site_a,assert_user_display_mode_browser_internal_site_a,launch_internal_site_a,assert_tab_created,install_omnibox_or_menu,assert_window_created
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,navigate_browser_in_scope_site_a,assert_install_icon_shown
+,navigate_installable_site_a,install_omnibox_or_menu,assert_window_created,assert_user_display_mode_standalone_internal_site_a,assert_manifest_display_mode_standalone_internal_site_a,uninstall_internal_site_a,list_apps_internal,assert_app_not_in_list_site_a
+,navigate_installable_site_a,install_create_shortcut_tabbed,user_signin
+,navigate_installable_site_a,install_create_shortcut_tabbed,list_apps_internal
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,navigate_browser_in_scope_site_a,assert_launch_icon_shown
+,navigate_installable_site_a,install_create_shortcut_tabbed,set_open_in_window_internal_site_a,assert_user_display_mode_standalone_internal_site_a,launch_internal_site_a,assert_window_created
diff --git a/chrome/test/webapps/script_test_files/doc_examples/empty_partial_coverage.csv b/chrome/test/webapps/script_test_files/doc_examples/empty_partial_coverage.csv
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/chrome/test/webapps/script_test_files/doc_examples/empty_partial_coverage.csv
@@ -0,0 +1 @@
+
diff --git a/chrome/test/webapps/script_test_files/doc_examples/realistic_actions.csv b/chrome/test/webapps/script_test_files/doc_examples/realistic_actions.csv
new file mode 100644
index 0000000..90e69a9
--- /dev/null
+++ b/chrome/test/webapps/script_test_files/doc_examples/realistic_actions.csv
@@ -0,0 +1,14 @@
+
+add_policy_app_windowed_shortcut_site_a,,
+assert_platform_shortcut_exists_site_a,,Y,
+assert_app_in_list_windowed_site_a,,Y,
+assert_install_icon_shown,,Y,
+assert_window_created,,Y,
+assert_window_title_site_a,,Y,
+install_internal_site_a,,,
+install_using_create_shortcut,,,
+install_using_omnibox_icon,,,
+list_apps,,,
+navigate_to_installable_site_a,,,
+remove_policy_app_site_a,,,
+user_signin,,,
diff --git a/chrome/test/webapps/script_test_files/doc_examples/realistic_coverage.csv b/chrome/test/webapps/script_test_files/doc_examples/realistic_coverage.csv
new file mode 100644
index 0000000..be7e43a
--- /dev/null
+++ b/chrome/test/webapps/script_test_files/doc_examples/realistic_coverage.csv
@@ -0,0 +1,7 @@
+
+
+navigate_to_installable_site_a,assert_install_icon_shown
+navigate_to_installable_site_a,install_using_omnibox_icon,list_apps,assert_app_in_list_windowed_site_a
+navigate_to_installable_site_a,install_using_omnibox_icon,assert_window_created
+navigate_to_installable_site_a,install_using_omnibox_icon,assert_window_title_site_a
+navigate_to_installable_site_a,install_using_create_shortcut,assert_window_created
diff --git a/chrome/test/webapps/script_test_files/doc_examples/realistic_framework_actions.csv b/chrome/test/webapps/script_test_files/doc_examples/realistic_framework_actions.csv
new file mode 100644
index 0000000..ae3169e
--- /dev/null
+++ b/chrome/test/webapps/script_test_files/doc_examples/realistic_framework_actions.csv
@@ -0,0 +1,7 @@
+
+assert_install_icon_shown
+assert_window_created
+assert_window_title_site_a
+install_using_create_shortcut
+install_internal_site_a
+navigate_to_installable_site_a
diff --git a/chrome/test/webapps/script_test_files/doc_examples/realistic_partial_coverage.csv b/chrome/test/webapps/script_test_files/doc_examples/realistic_partial_coverage.csv
new file mode 100644
index 0000000..865174f
--- /dev/null
+++ b/chrome/test/webapps/script_test_files/doc_examples/realistic_partial_coverage.csv
@@ -0,0 +1,3 @@
+
+navigate_to_installable_site_a; install_using_omnibox_icon,install_internal_site_a
+navigate_to_installable_site_a; install_using_create_shortcut,install_internal_site_a
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.cc b/chromeos/dbus/shill/fake_shill_manager_client.cc
index db1d8ec..73ae313 100644
--- a/chromeos/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/dbus/shill/fake_shill_manager_client.cc
@@ -47,7 +47,7 @@
 struct ValueEquals {
   explicit ValueEquals(const base::Value* first) : first_(first) {}
   bool operator()(const base::Value* second) const {
-    return first_->Equals(second);
+    return *first_ == *second;
   }
   const base::Value* first_;
 };
diff --git a/chromeos/dbus/shill/shill_client_unittest_base.cc b/chromeos/dbus/shill/shill_client_unittest_base.cc
index b70cbc2..247f4e8 100644
--- a/chromeos/dbus/shill/shill_client_unittest_base.cc
+++ b/chromeos/dbus/shill/shill_client_unittest_base.cc
@@ -55,7 +55,7 @@
 
 bool ValueMatcher::MatchAndExplain(const base::Value& value,
                                    MatchResultListener* listener) const {
-  return expected_value_->Equals(&value);
+  return *expected_value_ == value;
 }
 
 void ValueMatcher::DescribeTo(::std::ostream* os) const {
@@ -181,7 +181,7 @@
     const std::string& name,
     const base::Value& value) {
   EXPECT_EQ(expected_name, name);
-  EXPECT_TRUE(expected_value->Equals(&value));
+  EXPECT_EQ(*expected_value, value);
 }
 
 // static
@@ -243,7 +243,7 @@
   EXPECT_EQ(expected_string, str);
   std::unique_ptr<base::Value> value(dbus::PopDataAsValue(reader));
   ASSERT_TRUE(value.get());
-  EXPECT_TRUE(value->Equals(expected_value));
+  EXPECT_EQ(*value, *expected_value);
   EXPECT_FALSE(reader->HasMoreData());
 }
 
@@ -289,7 +289,7 @@
     ASSERT_TRUE(value.get());
     const base::Value* expected_value = expected_dictionary->FindKey(key);
     ASSERT_TRUE(expected_value);
-    EXPECT_TRUE(value->Equals(expected_value));
+    EXPECT_EQ(*value, *expected_value);
   }
 }
 
diff --git a/chromeos/network/cellular_esim_uninstall_handler.cc b/chromeos/network/cellular_esim_uninstall_handler.cc
index b5202ace..5b692f5 100644
--- a/chromeos/network/cellular_esim_uninstall_handler.cc
+++ b/chromeos/network/cellular_esim_uninstall_handler.cc
@@ -76,7 +76,7 @@
     UninstallRequestCallback callback) {
   uninstall_requests_.push_back(std::make_unique<UninstallRequest>(
       iccid, esim_profile_path, euicc_path, std::move(callback)));
-  ProcessUninstallRequest();
+  ProcessPendingUninstallRequests();
 }
 
 void CellularESimUninstallHandler::OnESimProfileListUpdated() {
@@ -92,68 +92,65 @@
   CheckStaleESimServices();
 }
 
-void CellularESimUninstallHandler::ProcessUninstallRequest() {
-  if (uninstall_requests_.empty()) {
-    TransitionToUninstallState(UninstallState::kIdle);
+void CellularESimUninstallHandler::ProcessPendingUninstallRequests() {
+  // No requests to process.
+  if (uninstall_requests_.empty())
     return;
-  }
 
-  if (state_ != UninstallState::kIdle) {
-    // Additional uninstall requests are queued. Skip processing a new one while
-    // another is in progress.
+  // Another uninstall request is in progress. Do not process a new request
+  // until the previous one has completed
+  if (state_ != UninstallState::kIdle)
     return;
-  }
 
-  curr_request_network_state_ = nullptr;
-  NET_LOG(DEBUG) << "Starting Uninstall Request iccid="
-                 << uninstall_requests_.front()->iccid;
-  TransitionToUninstallState(UninstallState::kDisconnectingNetwork);
+  NET_LOG(EVENT) << "Starting eSIM uninstall. ICCID: "
+                 << GetIccidForCurrentRequest();
+  TransitionToUninstallState(UninstallState::kCheckingNetworkState);
+  CheckNetworkState();
 }
 
 void CellularESimUninstallHandler::TransitionToUninstallState(
     UninstallState next_state) {
-  NET_LOG(DEBUG) << "ESim Profile Uninstaller changing state " << state_
-                 << " to " << next_state;
+  NET_LOG(EVENT) << "CellularESimUninstallHandler state: " << state_ << " => "
+                 << next_state;
   state_ = next_state;
-  switch (state_) {
-    case UninstallState::kIdle:
-      // Uninstallation has not started. Do nothing.
-      break;
-    case UninstallState::kDisconnectingNetwork:
-      AttemptNetworkDisconnectIfRequired();
-      break;
-    case UninstallState::kInhibitingShill:
-      AttemptShillInhibit();
-      break;
-    case UninstallState::kRequestingInstalledProfiles:
-      AttemptRequestInstalledProfiles();
-      break;
-    case UninstallState::kDisablingProfile:
-      AttemptDisableProfileIfRequired();
-      break;
-    case UninstallState::kUninstallingProfile:
-      AttemptUninstallProfile();
-      break;
-    case UninstallState::kRemovingShillService:
-      AttemptRemoveShillService();
-      break;
-    case UninstallState::kSuccess:
-    case UninstallState::kFailure:
-      std::move(uninstall_requests_.front()->callback)
-          .Run(state_ == UninstallState::kSuccess);
-      uninstall_requests_.pop_front();
-      ProcessUninstallRequest();
-      break;
-  }
 }
 
-void CellularESimUninstallHandler::AttemptNetworkDisconnectIfRequired() {
-  const std::string& iccid = uninstall_requests_.front()->iccid;
+void CellularESimUninstallHandler::CompleteCurrentRequest(bool success) {
+  DCHECK(state_ != UninstallState::kIdle);
 
-  curr_request_network_state_ = GetNetworkStateForIccid(iccid);
-  if (!curr_request_network_state_) {
-    NET_LOG(ERROR) << "Unable to get network state for iccid=" << iccid;
-    TransitionToUninstallState(UninstallState::kFailure);
+  NET_LOG(EVENT) << "Completed uninstall request for ICCID "
+                 << GetIccidForCurrentRequest() << ". Success = " << success;
+  std::move(uninstall_requests_.front()->callback).Run(success);
+  uninstall_requests_.pop_front();
+
+  TransitionToUninstallState(UninstallState::kIdle);
+  ProcessPendingUninstallRequests();
+}
+
+const std::string& CellularESimUninstallHandler::GetIccidForCurrentRequest()
+    const {
+  return uninstall_requests_.front()->iccid;
+}
+
+const NetworkState*
+CellularESimUninstallHandler::GetNetworkStateForCurrentRequest() const {
+  for (auto* const network : GetESimCellularNetworks()) {
+    if (network->iccid() == GetIccidForCurrentRequest()) {
+      return network;
+    }
+  }
+
+  return nullptr;
+}
+
+void CellularESimUninstallHandler::CheckNetworkState() {
+  DCHECK_EQ(state_, UninstallState::kCheckingNetworkState);
+
+  const NetworkState* network = GetNetworkStateForCurrentRequest();
+  if (!network) {
+    NET_LOG(ERROR) << "Unable to find eSIM network with ICCID "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
     return;
   }
 
@@ -161,25 +158,54 @@
   // Skip directly to configuration removal.
   if (!uninstall_requests_.front()->esim_profile_path) {
     TransitionToUninstallState(UninstallState::kRemovingShillService);
+    AttemptRemoveShillService();
     return;
   }
 
-  if (!curr_request_network_state_->IsNonShillCellularNetwork() &&
-      curr_request_network_state_->IsConnectedState()) {
-    network_connection_handler_->DisconnectNetwork(
-        curr_request_network_state_->path(),
-        base::BindOnce(
-            &CellularESimUninstallHandler::TransitionToUninstallState,
-            weak_ptr_factory_.GetWeakPtr(), UninstallState::kInhibitingShill),
-        base::BindOnce(&CellularESimUninstallHandler::OnNetworkHandlerError,
-                       weak_ptr_factory_.GetWeakPtr()));
+  // If the network is connected, disconnect it before we attempt to uninstall
+  // the associated profile.
+  if (network->IsConnectedState()) {
+    TransitionToUninstallState(UninstallState::kDisconnectingNetwork);
+    AttemptNetworkDisconnect(network);
     return;
   }
 
   TransitionToUninstallState(UninstallState::kInhibitingShill);
+  AttemptShillInhibit();
+}
+
+void CellularESimUninstallHandler::AttemptNetworkDisconnect(
+    const NetworkState* network) {
+  DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
+
+  network_connection_handler_->DisconnectNetwork(
+      network->path(),
+      base::BindOnce(&CellularESimUninstallHandler::OnDisconnectSuccess,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&CellularESimUninstallHandler::OnDisconnectFailure,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CellularESimUninstallHandler::OnDisconnectSuccess() {
+  DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
+
+  TransitionToUninstallState(UninstallState::kInhibitingShill);
+  AttemptShillInhibit();
+}
+
+void CellularESimUninstallHandler::OnDisconnectFailure(
+    const std::string& error_name,
+    std::unique_ptr<base::DictionaryValue> error_data) {
+  DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
+
+  NET_LOG(ERROR) << "Failed disconnecting network with ICCID "
+                 << GetIccidForCurrentRequest();
+  CompleteCurrentRequest(/*success=*/false);
 }
 
 void CellularESimUninstallHandler::AttemptShillInhibit() {
+  DCHECK_EQ(state_, UninstallState::kInhibitingShill);
+
   cellular_inhibitor_->InhibitCellularScanning(
       CellularInhibitor::InhibitReason::kRemovingProfile,
       base::BindOnce(&CellularESimUninstallHandler::OnShillInhibit,
@@ -188,18 +214,26 @@
 
 void CellularESimUninstallHandler::OnShillInhibit(
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
+  DCHECK_EQ(state_, UninstallState::kInhibitingShill);
+
   if (!inhibit_lock) {
-    NET_LOG(ERROR) << "Error inhbiting shill";
-    TransitionToUninstallState(UninstallState::kFailure);
+    NET_LOG(ERROR) << "Error inhbiting Shill during uninstall for ICCID "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
     return;
   }
+
   // Save lock in the uninstall request so that it will be released when the
   // request is popped.
   uninstall_requests_.front()->inhibit_lock = std::move(inhibit_lock);
+
   TransitionToUninstallState(UninstallState::kRequestingInstalledProfiles);
+  AttemptRequestInstalledProfiles();
 }
 
 void CellularESimUninstallHandler::AttemptRequestInstalledProfiles() {
+  DCHECK_EQ(state_, UninstallState::kRequestingInstalledProfiles);
+
   cellular_esim_profile_handler_->RefreshProfileList(
       *uninstall_requests_.front()->euicc_path,
       base::BindOnce(&CellularESimUninstallHandler::OnRefreshProfileListResult,
@@ -209,119 +243,136 @@
 
 void CellularESimUninstallHandler::OnRefreshProfileListResult(
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
+  DCHECK_EQ(state_, UninstallState::kRequestingInstalledProfiles);
+
   if (!inhibit_lock) {
-    NET_LOG(ERROR) << "Error refreshing profile list; state=" << state_;
-    TransitionToUninstallState(UninstallState::kFailure);
+    NET_LOG(ERROR) << "Error refreshing profile list during uninstall for "
+                   << "ICCID " << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
     return;
   }
 
+  // Save lock back to the uninstall request since we will continue to perform
+  // additional eSIM operations.
   uninstall_requests_.front()->inhibit_lock = std::move(inhibit_lock);
+
   TransitionToUninstallState(UninstallState::kDisablingProfile);
+  AttemptDisableProfile();
 }
 
-void CellularESimUninstallHandler::AttemptDisableProfileIfRequired() {
+void CellularESimUninstallHandler::AttemptDisableProfile() {
+  DCHECK_EQ(state_, UninstallState::kDisablingProfile);
+
   const dbus::ObjectPath& esim_profile_path =
       *uninstall_requests_.front()->esim_profile_path;
   HermesProfileClient::Properties* esim_profile_properties =
       HermesProfileClient::Get()->GetProperties(esim_profile_path);
 
   if (!esim_profile_properties) {
-    NET_LOG(ERROR) << "Unable to find esim profile to be uninstalled";
-    TransitionToUninstallState(UninstallState::kFailure);
+    NET_LOG(ERROR) << "eSIM profile not exposed by Hermes. ICCID: "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
     return;
   }
 
   if (esim_profile_properties->state().value() !=
       hermes::profile::State::kActive) {
-    NET_LOG(DEBUG) << "Profile is not active skipping disable profile state="
+    NET_LOG(EVENT) << "eSIM profile with ICCID " << GetIccidForCurrentRequest()
+                   << "is not active and does "
+                   << "not need to be disabled. State: "
                    << esim_profile_properties->state().value();
     TransitionToUninstallState(UninstallState::kUninstallingProfile);
+    AttemptUninstallProfile();
     return;
   }
 
   HermesProfileClient::Get()->DisableCarrierProfile(
       esim_profile_path,
-      base::BindOnce(&CellularESimUninstallHandler::
-                         TransitionUninstallStateOnHermesSuccess,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     UninstallState::kUninstallingProfile));
+      base::BindOnce(&CellularESimUninstallHandler::OnDisableProfile,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CellularESimUninstallHandler::OnDisableProfile(
+    HermesResponseStatus status) {
+  DCHECK_EQ(state_, UninstallState::kDisablingProfile);
+
+  bool success = status == HermesResponseStatus::kSuccess ||
+                 status == HermesResponseStatus::kErrorAlreadyDisabled;
+  if (!success) {
+    NET_LOG(ERROR) << "Failed to disable profile for ICCID "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
+    return;
+  }
+
+  TransitionToUninstallState(UninstallState::kUninstallingProfile);
+  AttemptUninstallProfile();
 }
 
 void CellularESimUninstallHandler::AttemptUninstallProfile() {
+  DCHECK_EQ(state_, UninstallState::kUninstallingProfile);
+
   HermesEuiccClient::Get()->UninstallProfile(
       *uninstall_requests_.front()->euicc_path,
       *uninstall_requests_.front()->esim_profile_path,
-      base::BindOnce(&CellularESimUninstallHandler::
-                         TransitionUninstallStateOnHermesSuccess,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     UninstallState::kRemovingShillService));
+      base::BindOnce(&CellularESimUninstallHandler::OnUninstallProfile,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CellularESimUninstallHandler::OnUninstallProfile(
+    HermesResponseStatus status) {
+  DCHECK_EQ(state_, UninstallState::kUninstallingProfile);
+
+  if (status != HermesResponseStatus::kSuccess) {
+    NET_LOG(ERROR) << "Failed to uninstall profile for ICCID "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
+    return;
+  }
+
+  TransitionToUninstallState(UninstallState::kRemovingShillService);
+  AttemptRemoveShillService();
 }
 
 void CellularESimUninstallHandler::AttemptRemoveShillService() {
-  DCHECK(curr_request_network_state_);
+  DCHECK_EQ(state_, UninstallState::kRemovingShillService);
+
+  const NetworkState* network = GetNetworkStateForCurrentRequest();
+  if (!network) {
+    NET_LOG(ERROR) << "Unable to find eSIM network with ICCID "
+                   << GetIccidForCurrentRequest();
+    CompleteCurrentRequest(/*success=*/false);
+    return;
+  }
+
   // Return success immediately for non-shill eSIM cellular networks since we
   // don't know the actual shill service path. This stub non-shill service will
   // be removed automatically when the eSIM profile list updates.
-  if (curr_request_network_state_->IsNonShillCellularNetwork()) {
-    TransitionToUninstallState(UninstallState::kSuccess);
+  if (network->IsNonShillCellularNetwork()) {
+    CompleteCurrentRequest(/*success=*/true);
     return;
   }
 
   network_configuration_handler_->RemoveConfiguration(
-      curr_request_network_state_->path(), base::nullopt,
-      base::BindOnce(&CellularESimUninstallHandler::TransitionToUninstallState,
-                     weak_ptr_factory_.GetWeakPtr(), UninstallState::kSuccess),
-      base::BindOnce(&CellularESimUninstallHandler::OnNetworkHandlerError,
+      network->path(), base::nullopt,
+      base::BindOnce(&CellularESimUninstallHandler::OnRemoveServiceSuccess,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&CellularESimUninstallHandler::OnRemoveServiceFailure,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void CellularESimUninstallHandler::TransitionUninstallStateOnHermesSuccess(
-    UninstallState next_state,
-    HermesResponseStatus status) {
-  bool success = status == HermesResponseStatus::kSuccess;
-
-  // If we try to disable and "fail" with an already-disabled error, count this
-  // as a success.
-  if (state_ == UninstallState::kDisablingProfile)
-    success |= status == HermesResponseStatus::kErrorAlreadyDisabled;
-
-  if (!success) {
-    NET_LOG(ERROR) << "Hermes error on uninstallation state=" << state_
-                   << " status=" << static_cast<int>(status);
-    TransitionToUninstallState(UninstallState::kFailure);
-    return;
-  }
-
-  TransitionToUninstallState(next_state);
+void CellularESimUninstallHandler::OnRemoveServiceSuccess() {
+  DCHECK_EQ(state_, UninstallState::kRemovingShillService);
+  CompleteCurrentRequest(/*success=*/true);
 }
 
-void CellularESimUninstallHandler::TransitionUninstallStateOnSuccessBoolean(
-    UninstallState next_state,
-    bool success) {
-  if (!success) {
-    NET_LOG(ERROR) << "Error on uninstallation state=" << state_;
-    TransitionToUninstallState(UninstallState::kFailure);
-    return;
-  }
-  TransitionToUninstallState(next_state);
-}
-
-void CellularESimUninstallHandler::OnNetworkHandlerError(
+void CellularESimUninstallHandler::OnRemoveServiceFailure(
     const std::string& error_name,
     std::unique_ptr<base::DictionaryValue> error_data) {
-  NET_LOG(ERROR) << "Network handler error at state " << state_
-                 << " error_name=" << error_name;
-  TransitionToUninstallState(UninstallState::kFailure);
-}
-
-const NetworkState* CellularESimUninstallHandler::GetNetworkStateForIccid(
-    const std::string& iccid) {
-  for (auto* const network : GetESimCellularNetworks()) {
-    if (network->iccid() == iccid) {
-      return network;
-    }
-  }
-  return nullptr;
+  DCHECK_EQ(state_, UninstallState::kRemovingShillService);
+  NET_LOG(ERROR) << "Error removing service with ICCID "
+                 << GetIccidForCurrentRequest() << ". Error: " << error_name;
+  CompleteCurrentRequest(/*success=*/false);
 }
 
 void CellularESimUninstallHandler::CheckStaleESimServices() {
@@ -344,23 +395,36 @@
     if (esim_iccids.contains(network_state->iccid()))
       continue;
 
+    // If we have not yet refreshed profiles for this EUICC, do not attempt to
+    // remove the service. This ensures that we don't accidentally remove
+    // services for eSIM profile which are in the process of being refreshed.
+    // See b/186753024 for details.
+    if (!cellular_esim_profile_handler_->HasRefreshedProfilesForEuicc(
+            network_state->eid())) {
+      NET_LOG(DEBUG) << "No eSIM profile for Shill service with ICCID "
+                     << network_state->iccid() << ". Not counted as stale "
+                     << "because we have not yet refreshed profiles from the "
+                     << "associated EUICC";
+      continue;
+    }
+
     // Skip if an uninstall request is already queued for this service.
     if (HasQueuedRequest(network_state->iccid()))
       continue;
 
-    NET_LOG(DEBUG) << "Queueing removal for stale shill config. iccid="
-                   << network_state->iccid()
+    NET_LOG(EVENT) << "Queueing removal for stale shill config. iccid="
+                   << network_state->iccid() << ", "
                    << "network path=" << network_state->path();
     uninstall_requests_.push_back(std::make_unique<UninstallRequest>(
         network_state->iccid(), /*esim_profile_path=*/base::nullopt,
         /*euicc_path=*/base::nullopt,
         base::BindOnce(&OnRemoveStaleShillService, network_state->path())));
   }
-  ProcessUninstallRequest();
+  ProcessPendingUninstallRequests();
 }
 
 NetworkStateHandler::NetworkStateList
-CellularESimUninstallHandler::GetESimCellularNetworks() {
+CellularESimUninstallHandler::GetESimCellularNetworks() const {
   NetworkStateHandler::NetworkStateList network_list;
   network_state_handler_->GetNetworkListByType(
       NetworkTypePattern::Cellular(), /*configured_only=*/false,
@@ -394,6 +458,9 @@
     case CellularESimUninstallHandler::UninstallState::kIdle:
       stream << "[Idle]";
       break;
+    case CellularESimUninstallHandler::UninstallState::kCheckingNetworkState:
+      stream << "[Checking network state]";
+      break;
     case CellularESimUninstallHandler::UninstallState::kInhibitingShill:
       stream << "[Inhibiting Shill]";
       break;
@@ -413,12 +480,6 @@
     case CellularESimUninstallHandler::UninstallState::kRemovingShillService:
       stream << "[Removing Shill Service]";
       break;
-    case CellularESimUninstallHandler::UninstallState::kSuccess:
-      stream << "[Success]";
-      break;
-    case CellularESimUninstallHandler::UninstallState::kFailure:
-      stream << "[Failure]";
-      break;
   }
   return stream;
 }
diff --git a/chromeos/network/cellular_esim_uninstall_handler.h b/chromeos/network/cellular_esim_uninstall_handler.h
index 2a50a8d..9dbd66e 100644
--- a/chromeos/network/cellular_esim_uninstall_handler.h
+++ b/chromeos/network/cellular_esim_uninstall_handler.h
@@ -82,14 +82,13 @@
  private:
   enum class UninstallState {
     kIdle,
+    kCheckingNetworkState,
     kDisconnectingNetwork,
     kInhibitingShill,
     kRequestingInstalledProfiles,
     kDisablingProfile,
     kUninstallingProfile,
     kRemovingShillService,
-    kSuccess,
-    kFailure,
   };
   friend std::ostream& operator<<(std::ostream& stream,
                                   const UninstallState& step);
@@ -111,31 +110,46 @@
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
   };
 
-  void ProcessUninstallRequest();
+  void ProcessPendingUninstallRequests();
   void TransitionToUninstallState(UninstallState next_state);
-  void AttemptNetworkDisconnectIfRequired();
+  void CompleteCurrentRequest(bool success);
+
+  const std::string& GetIccidForCurrentRequest() const;
+  const NetworkState* GetNetworkStateForCurrentRequest() const;
+
+  void CheckNetworkState();
+
+  void AttemptNetworkDisconnect(const NetworkState* network);
+  void OnDisconnectSuccess();
+  void OnDisconnectFailure(const std::string& error_name,
+                           std::unique_ptr<base::DictionaryValue> error_data);
+
   void AttemptShillInhibit();
   void OnShillInhibit(
       std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock);
+
   void AttemptRequestInstalledProfiles();
   void OnRefreshProfileListResult(
       std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock);
-  void AttemptDisableProfileIfRequired();
+
+  void AttemptDisableProfile();
+  void OnDisableProfile(HermesResponseStatus status);
+
   void AttemptUninstallProfile();
+  void OnUninstallProfile(HermesResponseStatus status);
+
   void AttemptRemoveShillService();
-  void TransitionUninstallStateOnHermesSuccess(UninstallState next_state,
-                                               HermesResponseStatus status);
-  void TransitionUninstallStateOnSuccessBoolean(UninstallState next_state,
-                                                bool success);
-  void OnNetworkHandlerError(const std::string& error_name,
-                             std::unique_ptr<base::DictionaryValue> error_data);
-  const NetworkState* GetNetworkStateForIccid(const std::string& iccid);
+  void OnRemoveServiceSuccess();
+  void OnRemoveServiceFailure(
+      const std::string& error_name,
+      std::unique_ptr<base::DictionaryValue> error_data);
+
   void CheckStaleESimServices();
-  NetworkStateHandler::NetworkStateList GetESimCellularNetworks();
+
+  NetworkStateHandler::NetworkStateList GetESimCellularNetworks() const;
   bool HasQueuedRequest(const std::string& iccid) const;
 
   UninstallState state_ = UninstallState::kIdle;
-  const NetworkState* curr_request_network_state_ = nullptr;
   base::circular_deque<std::unique_ptr<UninstallRequest>> uninstall_requests_;
 
   CellularInhibitor* cellular_inhibitor_ = nullptr;
diff --git a/chromeos/network/cellular_esim_uninstall_handler_unittest.cc b/chromeos/network/cellular_esim_uninstall_handler_unittest.cc
index a9cd715..f0cae54 100644
--- a/chromeos/network/cellular_esim_uninstall_handler_unittest.cc
+++ b/chromeos/network/cellular_esim_uninstall_handler_unittest.cc
@@ -133,10 +133,11 @@
   }
 
   void UninstallESim(base::RunLoop& run_loop,
+                     const std::string& iccid,
                      const std::string& carrier_profile_path,
                      bool& status) {
     cellular_esim_uninstall_handler_->UninstallESim(
-        kTestCellularIccid, dbus::ObjectPath(carrier_profile_path),
+        iccid, dbus::ObjectPath(carrier_profile_path),
         dbus::ObjectPath(kDefaultEuiccPath),
         base::BindLambdaForTesting([&](bool status_result) {
           status = status_result;
@@ -173,6 +174,11 @@
     network_state_handler_->SyncStubCellularNetworks();
   }
 
+  void SetHasRefreshedProfiles(bool has_refreshed) {
+    cellular_esim_profile_handler_->SetHasRefreshedProfilesForEuicc(
+        kDefaultEid, has_refreshed);
+  }
+
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
 
@@ -195,7 +201,7 @@
 
   base::RunLoop run_loop;
   bool status;
-  UninstallESim(run_loop, kTestCarrierProfilePath, status);
+  UninstallESim(run_loop, kTestCellularIccid, kTestCarrierProfilePath, status);
   HandleNetworkDisconnect(/*should_fail=*/false);
   run_loop.Run();
 
@@ -216,7 +222,7 @@
 
   base::RunLoop run_loop;
   bool status;
-  UninstallESim(run_loop, kTestCarrierProfilePath, status);
+  UninstallESim(run_loop, kTestCellularIccid, kTestCarrierProfilePath, status);
   HandleNetworkDisconnect(/*should_fail=*/false);
   run_loop.Run();
 
@@ -237,7 +243,7 @@
 
   bool status;
   base::RunLoop run_loop;
-  UninstallESim(run_loop, kTestCarrierProfilePath, status);
+  UninstallESim(run_loop, kTestCellularIccid, kTestCarrierProfilePath, status);
   HandleNetworkDisconnect(/*should_fail=*/true);
   run_loop.Run();
   EXPECT_FALSE(status);
@@ -252,7 +258,7 @@
       HermesResponseStatus::kErrorUnknown);
   bool status;
   base::RunLoop run_loop;
-  UninstallESim(run_loop, kTestCarrierProfilePath, status);
+  UninstallESim(run_loop, kTestCellularIccid, kTestCarrierProfilePath, status);
   HandleNetworkDisconnect(/*should_fail=*/false);
   run_loop.Run();
   EXPECT_FALSE(status);
@@ -267,26 +273,29 @@
   // Make two uninstall requests back to back.
   bool status1, status2;
   base::RunLoop run_loop1, run_loop2;
-  UninstallESim(run_loop1, kTestCarrierProfilePath, status1);
-  UninstallESim(run_loop2, kTestCarrierProfilePath2, status2);
+
+  UninstallESim(run_loop1, kTestCellularIccid, kTestCarrierProfilePath,
+                status1);
+  UninstallESim(run_loop2, kTestCellularIccid2, kTestCarrierProfilePath2,
+                status2);
+
+  // Only the first profile is connected, so only one disconnect handler is
+  // needed.
   HandleNetworkDisconnect(/*should_fail=*/false);
-  HandleNetworkDisconnect(/*should_fail=*/true);
+
   run_loop1.Run();
   run_loop2.Run();
 
-  // Verify that only the first request succeeded.
+  // Verify that both requests succeeded.
   EXPECT_TRUE(status1);
-  EXPECT_FALSE(status2);
+  EXPECT_TRUE(status2);
   HermesEuiccClient::Properties* euicc_properties =
       HermesEuiccClient::Get()->GetProperties(
           dbus::ObjectPath(kDefaultEuiccPath));
   ASSERT_TRUE(euicc_properties);
-  EXPECT_EQ(1u, euicc_properties->installed_carrier_profiles().value().size());
-  EXPECT_EQ(
-      kTestCarrierProfilePath2,
-      euicc_properties->installed_carrier_profiles().value().front().value());
+  EXPECT_TRUE(euicc_properties->installed_carrier_profiles().value().empty());
   EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath));
-  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath2));
+  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath2));
 }
 
 TEST_F(CellularESimUninstallHandlerTest, StubCellularNetwork) {
@@ -301,7 +310,7 @@
   // Verify that removing the eSIM profile succeeds.
   base::RunLoop run_loop;
   bool success;
-  UninstallESim(run_loop, kTestCarrierProfilePath, success);
+  UninstallESim(run_loop, kTestCellularIccid, kTestCarrierProfilePath, success);
   run_loop.Run();
   EXPECT_TRUE(success);
 }
@@ -309,16 +318,34 @@
 TEST_F(CellularESimUninstallHandlerTest, RemovesShillOnlyServices) {
   Init();
   EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath));
+  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath2));
 
-  // Remove profile without removing service.
+  // Start without having refreshed profiles.
+  SetHasRefreshedProfiles(/*has_refreshed=*/false);
+
+  // Remove first profile without removing service. Both services should still
+  // exist, since removal of stale services only occurs if the EUICC has been
+  // refreshed.
   EXPECT_TRUE(
       HermesEuiccClient::Get()->GetTestInterface()->RemoveCarrierProfile(
           dbus::ObjectPath(kDefaultEuiccPath),
           dbus::ObjectPath(kTestCarrierProfilePath)));
   base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath));
+  EXPECT_TRUE(ESimServiceConfigExists(kTestNetworkServicePath2));
 
-  // Verify that stale service is also removed.
+  // Finish refreshing profiles.
+  SetHasRefreshedProfiles(/*has_refreshed=*/true);
+
+  // Remove the second profile without removing service. Both services should
+  // have been detected as "stale" and should now be removed.
+  EXPECT_TRUE(
+      HermesEuiccClient::Get()->GetTestInterface()->RemoveCarrierProfile(
+          dbus::ObjectPath(kDefaultEuiccPath),
+          dbus::ObjectPath(kTestCarrierProfilePath2)));
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath));
+  EXPECT_FALSE(ESimServiceConfigExists(kTestNetworkServicePath2));
 }
 
 }  // namespace chromeos
diff --git a/chromeos/network/managed_network_configuration_handler_impl.cc b/chromeos/network/managed_network_configuration_handler_impl.cc
index 1fb671df..1bb188c 100644
--- a/chromeos/network/managed_network_configuration_handler_impl.cc
+++ b/chromeos/network/managed_network_configuration_handler_impl.cc
@@ -533,7 +533,7 @@
     policies->per_network_config[guid] = base::WrapUnique(new_entry);
 
     base::DictionaryValue* old_entry = old_per_network_config[guid].get();
-    if (!old_entry || !old_entry->Equals(new_entry))
+    if (!old_entry || *old_entry != *new_entry)
       modified_policies.insert(guid);
   }
 
diff --git a/chromeos/network/onc/onc_merger.cc b/chromeos/network/onc/onc_merger.cc
index 6d048679..438ba19d1 100644
--- a/chromeos/network/onc/onc_merger.cc
+++ b/chromeos/network/onc/onc_merger.cc
@@ -331,15 +331,15 @@
 // Returns true if all not-null values in |values| are equal to |value|.
 bool AllPresentValuesEqual(const MergeSettingsAndPolicies::ValueParams& values,
                            const base::Value& value) {
-  if (values.user_policy && !value.Equals(values.user_policy))
+  if (values.user_policy && value != *values.user_policy)
     return false;
-  if (values.device_policy && !value.Equals(values.device_policy))
+  if (values.device_policy && value != *values.device_policy)
     return false;
-  if (values.user_setting && !value.Equals(values.user_setting))
+  if (values.user_setting && value != *values.user_setting)
     return false;
-  if (values.shared_setting && !value.Equals(values.shared_setting))
+  if (values.shared_setting && value != *values.shared_setting)
     return false;
-  if (values.active_setting && !value.Equals(values.active_setting))
+  if (values.active_setting && value != *values.active_setting)
     return false;
   return true;
 }
diff --git a/chromeos/network/shill_property_util.cc b/chromeos/network/shill_property_util.cc
index d82ad4d..de8515d94 100644
--- a/chromeos/network/shill_property_util.cc
+++ b/chromeos/network/shill_property_util.cc
@@ -326,7 +326,7 @@
     return false;
   }
 
-  return new_identifying.Equals(&old_identifying);
+  return new_identifying == old_identifying;
 }
 
 bool IsLoggableShillProperty(const std::string& key) {
diff --git a/chromeos/services/chromebox_for_meetings/public/mojom/cfm_browser.mojom b/chromeos/services/chromebox_for_meetings/public/mojom/cfm_browser.mojom
index 4d447b7..4b06cc7 100644
--- a/chromeos/services/chromebox_for_meetings/public/mojom/cfm_browser.mojom
+++ b/chromeos/services/chromebox_for_meetings/public/mojom/cfm_browser.mojom
@@ -16,6 +16,7 @@
                           string field_trial_states,
                           string enabled_features,
                           string disabled_features);
+
   // Returns detailed information on running processes as well system-wide
   // graphics driver memory (-1 if error).
   GetMemoryDetails@1()=>(array<ProcessData> process_data,
@@ -34,24 +35,34 @@
 struct ProcessMemoryInformation {
   // The process id.
   int32 pid@0;
+
   // The process version
   string version@1;
+
   // The process product name.
   string product_name@2;
+
   // The number of processes which this memory represents.
   int32 num_processes@3;
+
   // If this is a child process of Chrome, what type (i.e. plugin) it is.
   string process_type@4;
+
   // Number of open file descriptors in this process.
   int32 num_open_fds@5;
+
   // Maximum number of file descriptors that can be opened in this process.
   int32 open_fds_soft_limit@6;
+
   // If this is a renderer process, what type it is.
   string renderer_type@7;
+
   // A collection of titles used, i.e. for a tab it'll show all the page titles.
   array<string> titles@8;
+
   // Consistent memory metric for all platforms.
   int32 private_memory_footprint_kb@9;
+
   // iff process is process_type extension add additional info
   array<ExtensionData> extension_info@10;
 };
@@ -60,7 +71,10 @@
 struct ProcessData {
   // The qualified name of the browser
   string name@0;
+
   // The name of the process running
   string process_name@1;
+
+  // List of child processes.
   array<ProcessMemoryInformation> processes@2;
 };
diff --git a/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_info.mojom b/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_info.mojom
index d731c89..7ee9ee92 100644
--- a/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_info.mojom
+++ b/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_info.mojom
@@ -32,10 +32,13 @@
   // Timestamp in milliseconds (javatime)
   // From DMServer when server issued response (Java time)
   int64 timestamp_ms@0;
+
   // PolicyData::DeviceID
   string? device_id@1;
+
   // PolicyData::ServiceAccountIdentity
   string? service_account_email_address@2;
+
   // PolicyData::GaiaId
   int64 service_account_gaia_id@3;
 };
@@ -44,16 +47,22 @@
 struct SysInfo {
   // Returns the kernel version
   string kernel_version@0;
+
   // /etc/lsb-release: CHROMEOS_RELEASE_VERSION
   string? release_version@1;
+
   // /etc/lsb-release: CHROMEOS_RELEASE_BUILD_TYPE
   string? release_build_type@2;
+
   // /etc/lsb-release: CHROMEOS_RELEASE_TRACK
   string? release_track@3;
+
   // /etc/lsb-release: CHROMEOS_RELEASE_CHROME_MILESTONE
   string? release_milestone@4;
+
   // The full browser version number
   string? browser_version@5;
-  // Returns the current
+
+  // Returns the current channel name
   string? channel_name@6;
 };
diff --git a/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_logger.mojom b/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_logger.mojom
index d7393b8c1..883a178 100644
--- a/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_logger.mojom
+++ b/chromeos/services/chromebox_for_meetings/public/mojom/meet_devices_logger.mojom
@@ -46,3 +46,4 @@
   // Adds a |LoggerStateObserver| to monitor |MeetDevicesLogger|'s state.
   AddStateObserver@1(pending_remote<LoggerStateObserver> pending_observer);
 };
+
diff --git a/chromeos/services/device_sync/cryptauth_device_manager_impl.cc b/chromeos/services/device_sync/cryptauth_device_manager_impl.cc
index 2b27ad3..cfb10ca 100644
--- a/chromeos/services/device_sync/cryptauth_device_manager_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_device_manager_impl.cc
@@ -706,8 +706,9 @@
     devices_as_list->Append(std::move(device_dictionary));
   }
 
-  bool unlock_keys_changed = !devices_as_list->Equals(
-      pref_service_->GetList(prefs::kCryptAuthDeviceSyncUnlockKeys));
+  bool unlock_keys_changed =
+      *devices_as_list !=
+      *pref_service_->GetList(prefs::kCryptAuthDeviceSyncUnlockKeys);
   {
     ListPrefUpdate update(pref_service_, prefs::kCryptAuthDeviceSyncUnlockKeys);
     update.Get()->Swap(devices_as_list.get());
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 9e0c7d6..cb376e0d 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -388,6 +388,8 @@
     "test/fake_wake_lock_instance.h",
     "test/fake_wallpaper_instance.cc",
     "test/fake_wallpaper_instance.h",
+    "test/fake_webapk_instance.cc",
+    "test/fake_webapk_instance.h",
     "test/test_browser_context.cc",
     "test/test_browser_context.h",
   ]
diff --git a/components/arc/intent_helper/link_handler_model.cc b/components/arc/intent_helper/link_handler_model.cc
index acc0e292..ab6a558 100644
--- a/components/arc/intent_helper/link_handler_model.cc
+++ b/components/arc/intent_helper/link_handler_model.cc
@@ -13,6 +13,7 @@
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/google/core/common/google_util.h"
 #include "url/url_util.h"
@@ -79,9 +80,8 @@
     return;
   instance->HandleUrl(url_.spec(), handlers_[handler_id]->package_name);
 
-  UMA_HISTOGRAM_ENUMERATION(
-      "Arc.UserInteraction",
-      arc::UserInteractionType::APP_STARTED_FROM_LINK_CONTEXT_MENU);
+  ArcMetricsService::RecordArcUserInteraction(
+      context_, arc::UserInteractionType::APP_STARTED_FROM_LINK_CONTEXT_MENU);
 }
 
 LinkHandlerModel::LinkHandlerModel() = default;
diff --git a/components/arc/metrics/arc_metrics_service.cc b/components/arc/metrics/arc_metrics_service.cc
index 2d16a43..3d581c4 100644
--- a/components/arc/metrics/arc_metrics_service.cc
+++ b/components/arc/metrics/arc_metrics_service.cc
@@ -19,7 +19,6 @@
 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_util.h"
-#include "components/arc/metrics/arc_metrics_constants.h"
 #include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/exo/wm_helper.h"
@@ -145,6 +144,23 @@
   app_kill_observers_.Clear();
 }
 
+// static
+void ArcMetricsService::RecordArcUserInteraction(
+    content::BrowserContext* context,
+    UserInteractionType type) {
+  DCHECK(context);
+  auto* service = GetForBrowserContext(context);
+  if (!service) {
+    LOG(WARNING) << "Cannot get ArcMetricsService for context " << context;
+    return;
+  }
+  service->RecordArcUserInteraction(type);
+}
+
+void ArcMetricsService::RecordArcUserInteraction(UserInteractionType type) {
+  UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction", type);
+}
+
 void ArcMetricsService::SetHistogramNamer(HistogramNamer histogram_namer) {
   histogram_namer_ = histogram_namer;
 }
@@ -375,9 +391,7 @@
     gamepad_interaction_recorded_ = false;
     return;
   }
-  UMA_HISTOGRAM_ENUMERATION(
-      "Arc.UserInteraction",
-      UserInteractionType::APP_CONTENT_WINDOW_INTERACTION);
+  RecordArcUserInteraction(UserInteractionType::APP_CONTENT_WINDOW_INTERACTION);
 }
 
 void ArcMetricsService::OnGamepadEvent(const ui::GamepadEvent& event) {
@@ -386,8 +400,7 @@
   if (gamepad_interaction_recorded_)
     return;
   gamepad_interaction_recorded_ = true;
-  UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
-                            UserInteractionType::GAMEPAD_INTERACTION);
+  RecordArcUserInteraction(UserInteractionType::GAMEPAD_INTERACTION);
 }
 
 void ArcMetricsService::OnTaskCreated(int32_t task_id,
diff --git a/components/arc/metrics/arc_metrics_service.h b/components/arc/metrics/arc_metrics_service.h
index a1c351c86..fc969f5 100644
--- a/components/arc/metrics/arc_metrics_service.h
+++ b/components/arc/metrics/arc_metrics_service.h
@@ -18,6 +18,7 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "components/arc/metrics/arc_metrics_constants.h"
 #include "components/arc/mojom/metrics.mojom.h"
 #include "components/arc/mojom/process.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
@@ -77,6 +78,10 @@
   // KeyedService overrides.
   void Shutdown() override;
 
+  // Records one of Arc.UserInteraction UMA stats. |context| cannot be null.
+  static void RecordArcUserInteraction(content::BrowserContext* context,
+                                       UserInteractionType type);
+
   // Sets the histogram namer. Required to not have a dependency on browser
   // codebase.
   void SetHistogramNamer(HistogramNamer histogram_namer);
@@ -193,6 +198,7 @@
     DISALLOW_COPY_AND_ASSIGN(AppLauncherObserver);
   };
 
+  void RecordArcUserInteraction(UserInteractionType type);
   void RequestProcessList();
   void ParseProcessList(std::vector<mojom::RunningAppProcessInfoPtr> processes);
 
diff --git a/components/arc/mojom/BUILD.gn b/components/arc/mojom/BUILD.gn
index 825de163..9cf3141 100644
--- a/components/arc/mojom/BUILD.gn
+++ b/components/arc/mojom/BUILD.gn
@@ -71,6 +71,7 @@
       "volume_mounter.mojom",
       "wake_lock.mojom",
       "wallpaper.mojom",
+      "webapk.mojom",
     ]
 
     public_deps = [
diff --git a/components/arc/mojom/arc_bridge.mojom b/components/arc/mojom/arc_bridge.mojom
index 5f61d2a..bddf7d1 100644
--- a/components/arc/mojom/arc_bridge.mojom
+++ b/components/arc/mojom/arc_bridge.mojom
@@ -61,10 +61,11 @@
 import "components/arc/mojom/volume_mounter.mojom";
 import "components/arc/mojom/wake_lock.mojom";
 import "components/arc/mojom/wallpaper.mojom";
+import "components/arc/mojom/webapk.mojom";
 
-// Next MinVersion: 57
+// Next MinVersion: 58
 // Deprecated method IDs: 101, 105, 121
-// Next method ID: 162
+// Next method ID: 163
 interface ArcBridgeHost {
   // Keep the entries alphabetical. In order to do so without breaking
   // compatibility with the ARC instance, explicitly assign each interface a
@@ -298,4 +299,8 @@
   // Notifies Chrome that the WallpaperInstance interface is ready.
   [MinVersion=18] OnWallpaperInstanceReady@124(
       pending_remote<WallpaperInstance> instance_remote);
+
+  // Notifies Chrome that the WebApkInstance interface is ready.
+  [MinVersion=57] OnWebApkInstanceReady@162(
+      pending_remote<WebApkInstance> instance_ptr);
 };
diff --git a/components/arc/mojom/webapk.mojom b/components/arc/mojom/webapk.mojom
new file mode 100644
index 0000000..a1fc76b
--- /dev/null
+++ b/components/arc/mojom/webapk.mojom
@@ -0,0 +1,36 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Next MinVersion: 1
+
+module arc.mojom;
+
+[Extensible]
+enum WebApkInstallResult {
+  kSuccess = 0,
+  kErrorUnknown = 1,
+  // Errors while communicating with PlayInstallService.
+  kErrorServiceCommunication = 2,
+  kErrorServiceTimeout = 3,
+  // Errors returned from PlayInstallService directly.
+  kErrorCallerVerificationFailed = 4,
+  kErrorPolicyViolation = 5,
+  kErrorApiDisabled = 6,
+  kErrorUnknownAccount = 7,
+  kErrorResolveNetworkError = 8,
+  kErrorResolveError = 9,
+  kErrorNotGoogleSigned = 10,
+};
+
+// Allows Chrome to install and manage WebAPKs inside ARC.
+// Next method ID: 1
+interface WebApkInstance {
+
+  // Install or update a WebAPK with the given |package_name|.
+  InstallWebApk@0(string package_name,
+                  uint32 version,
+                  string app_name,
+                  string token) =>
+                      (WebApkInstallResult result);
+};
diff --git a/components/arc/net/always_on_vpn_manager_unittest.cc b/components/arc/net/always_on_vpn_manager_unittest.cc
index 6690263..0e01643 100644
--- a/components/arc/net/always_on_vpn_manager_unittest.cc
+++ b/components/arc/net/always_on_vpn_manager_unittest.cc
@@ -7,9 +7,8 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
-#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_handler_test_helper.h"
 #include "components/arc/arc_prefs.h"
 #include "components/prefs/testing_pref_service.h"
 #include "content/public/test/browser_task_environment.h"
@@ -39,7 +38,7 @@
   bool success = false;
   std::string package_name;
   chromeos::ShillManagerClient* shill_manager =
-      chromeos::DBusThreadManager::Get()->GetShillManagerClient();
+      chromeos::ShillManagerClient::Get();
   base::RunLoop run_loop;
   shill_manager->GetProperties(
       base::BindOnce(&OnGetProperties, base::Unretained(&success),
@@ -59,22 +58,14 @@
   AlwaysOnVpnManagerTest() = default;
 
   void SetUp() override {
-    chromeos::DBusThreadManager::Initialize();
-    EXPECT_TRUE(chromeos::DBusThreadManager::Get()->IsUsingFakes());
-    chromeos::NetworkHandler::Initialize();
-    EXPECT_TRUE(chromeos::NetworkHandler::IsInitialized());
     arc::prefs::RegisterProfilePrefs(pref_service()->registry());
   }
 
-  void TearDown() override {
-    chromeos::NetworkHandler::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
-  }
-
   TestingPrefServiceSimple* pref_service() { return &pref_service_; }
 
  private:
   content::BrowserTaskEnvironment task_environment_;
+  chromeos::NetworkHandlerTestHelper network_handler_test_helper_;
   TestingPrefServiceSimple pref_service_;
 
   DISALLOW_COPY_AND_ASSIGN(AlwaysOnVpnManagerTest);
diff --git a/components/arc/session/arc_bridge_host_impl.cc b/components/arc/session/arc_bridge_host_impl.cc
index f3b4fb8..80fd0dd 100644
--- a/components/arc/session/arc_bridge_host_impl.cc
+++ b/components/arc/session/arc_bridge_host_impl.cc
@@ -68,6 +68,7 @@
 #include "components/arc/mojom/volume_mounter.mojom.h"
 #include "components/arc/mojom/wake_lock.mojom.h"
 #include "components/arc/mojom/wallpaper.mojom.h"
+#include "components/arc/mojom/webapk.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/mojo_channel.h"
 
@@ -428,6 +429,11 @@
                   std::move(wallpaper_remote));
 }
 
+void ArcBridgeHostImpl::OnWebApkInstanceReady(
+    mojo::PendingRemote<mojom::WebApkInstance> webapk_remote) {
+  OnInstanceReady(arc_bridge_service_->webapk(), std::move(webapk_remote));
+}
+
 size_t ArcBridgeHostImpl::GetNumMojoChannelsForTesting() const {
   return mojo_channels_.size();
 }
diff --git a/components/arc/session/arc_bridge_host_impl.h b/components/arc/session/arc_bridge_host_impl.h
index 02dc6e0..ea63b0e 100644
--- a/components/arc/session/arc_bridge_host_impl.h
+++ b/components/arc/session/arc_bridge_host_impl.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
 #include "components/arc/mojom/arc_bridge.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/connection_holder.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -176,6 +177,8 @@
       mojo::PendingRemote<mojom::WakeLockInstance> wake_lock_remote) override;
   void OnWallpaperInstanceReady(
       mojo::PendingRemote<mojom::WallpaperInstance> wallpaper_remote) override;
+  void OnWebApkInstanceReady(
+      mojo::PendingRemote<mojom::WebApkInstance> webapk_remote) override;
 
   size_t GetNumMojoChannelsForTesting() const;
 
diff --git a/components/arc/session/arc_bridge_service.h b/components/arc/session/arc_bridge_service.h
index 48525a1..5172090 100644
--- a/components/arc/session/arc_bridge_service.h
+++ b/components/arc/session/arc_bridge_service.h
@@ -111,6 +111,7 @@
 class WakeLockInstance;
 class WallpaperHost;
 class WallpaperInstance;
+class WebApkInstance;
 }  // namespace mojom
 
 // Holds Mojo channels which proxy to ARC side implementation. The actual
@@ -318,6 +319,7 @@
   wallpaper() {
     return &wallpaper_;
   }
+  ConnectionHolder<mojom::WebApkInstance>* webapk() { return &webapk_; }
 
  private:
   base::ObserverList<Observer> observer_list_;
@@ -394,6 +396,7 @@
       volume_mounter_;
   ConnectionHolder<mojom::WakeLockInstance, mojom::WakeLockHost> wake_lock_;
   ConnectionHolder<mojom::WallpaperInstance, mojom::WallpaperHost> wallpaper_;
+  ConnectionHolder<mojom::WebApkInstance> webapk_;
   DISALLOW_COPY_AND_ASSIGN(ArcBridgeService);
 };
 
diff --git a/components/arc/test/fake_arc_bridge_host.cc b/components/arc/test/fake_arc_bridge_host.cc
index 43ad1a2..15af1fc 100644
--- a/components/arc/test/fake_arc_bridge_host.cc
+++ b/components/arc/test/fake_arc_bridge_host.cc
@@ -251,4 +251,7 @@
 void FakeArcBridgeHost::OnWallpaperInstanceReady(
     mojo::PendingRemote<mojom::WallpaperInstance> wallpaper_remote) {}
 
+void FakeArcBridgeHost::OnWebApkInstanceReady(
+    mojo::PendingRemote<mojom::WebApkInstance> wallpaper_remote) {}
+
 }  // namespace arc
diff --git a/components/arc/test/fake_arc_bridge_host.h b/components/arc/test/fake_arc_bridge_host.h
index 0b0ff71..bbf42a4 100644
--- a/components/arc/test/fake_arc_bridge_host.h
+++ b/components/arc/test/fake_arc_bridge_host.h
@@ -155,6 +155,8 @@
       mojo::PendingRemote<mojom::WakeLockInstance> wakelock_remote) override;
   void OnWallpaperInstanceReady(
       mojo::PendingRemote<mojom::WallpaperInstance> wallpaper_remote) override;
+  void OnWebApkInstanceReady(
+      mojo::PendingRemote<mojom::WebApkInstance> webapk_instance) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FakeArcBridgeHost);
diff --git a/components/arc/test/fake_webapk_instance.cc b/components/arc/test/fake_webapk_instance.cc
new file mode 100644
index 0000000..34e23b0
--- /dev/null
+++ b/components/arc/test/fake_webapk_instance.cc
@@ -0,0 +1,21 @@
+// Copyright 2021 The Chromium 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/arc/test/fake_webapk_instance.h"
+
+namespace arc {
+
+FakeWebApkInstance::FakeWebApkInstance() = default;
+
+FakeWebApkInstance::~FakeWebApkInstance() = default;
+
+void FakeWebApkInstance::InstallWebApk(const std::string& package_name,
+                                       uint32_t version,
+                                       const std::string& app_name,
+                                       const std::string& token,
+                                       InstallWebApkCallback callback) {
+  handled_packages_.push_back(package_name);
+  std::move(callback).Run(install_result_);
+}
+}  // namespace arc
diff --git a/components/arc/test/fake_webapk_instance.h b/components/arc/test/fake_webapk_instance.h
new file mode 100644
index 0000000..e3303241
--- /dev/null
+++ b/components/arc/test/fake_webapk_instance.h
@@ -0,0 +1,44 @@
+// Copyright 2021 The Chromium 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_ARC_TEST_FAKE_WEBAPK_INSTANCE_H_
+#define COMPONENTS_ARC_TEST_FAKE_WEBAPK_INSTANCE_H_
+
+#include "components/arc/mojom/webapk.mojom.h"
+
+namespace arc {
+
+class FakeWebApkInstance : public mojom::WebApkInstance {
+ public:
+  FakeWebApkInstance();
+  FakeWebApkInstance(const FakeWebApkInstance&) = delete;
+  FakeWebApkInstance& operator=(const FakeWebApkInstance&) = delete;
+
+  ~FakeWebApkInstance() override;
+
+  // mojom::WebApkInstance overrides:
+  void InstallWebApk(const std::string& package_name,
+                     uint32_t version,
+                     const std::string& app_name,
+                     const std::string& token,
+                     InstallWebApkCallback callback) override;
+
+  const std::vector<std::string>& handled_packages() {
+    return handled_packages_;
+  }
+
+  void set_install_result(arc::mojom::WebApkInstallResult result) {
+    install_result_ = result;
+  }
+
+ private:
+  std::vector<std::string> handled_packages_;
+
+  arc::mojom::WebApkInstallResult install_result_ =
+      arc::mojom::WebApkInstallResult::kSuccess;
+};
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_TEST_FAKE_WEBAPK_INSTANCE_H_
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 4b2941e..524bba8 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -247,12 +247,6 @@
     "payments/payments_util.cc",
     "payments/payments_util.h",
     "payments/risk_data_loader.h",
-    "payments/strike_database.cc",
-    "payments/strike_database.h",
-    "payments/strike_database_integrator_base.cc",
-    "payments/strike_database_integrator_base.h",
-    "payments/strike_database_integrator_test_strike_database.cc",
-    "payments/strike_database_integrator_test_strike_database.h",
     "payments/webauthn_callback_types.h",
     "personal_data_manager.cc",
     "personal_data_manager.h",
@@ -263,6 +257,12 @@
     "randomized_encoder.h",
     "rationalization_util.cc",
     "rationalization_util.h",
+    "strike_database.cc",
+    "strike_database.h",
+    "strike_database_integrator_base.cc",
+    "strike_database_integrator_base.h",
+    "strike_database_integrator_test_strike_database.cc",
+    "strike_database_integrator_test_strike_database.h",
     "sync_utils.h",
     "ui/accessory_sheet_data.cc",
     "ui/accessory_sheet_data.h",
@@ -714,11 +714,11 @@
     "payments/payments_client_unittest.cc",
     "payments/payments_service_url_unittest.cc",
     "payments/payments_util_unittest.cc",
-    "payments/strike_database_integrator_test_strike_database_unittest.cc",
-    "payments/strike_database_unittest.cc",
     "personal_data_manager_unittest.cc",
     "randomized_encoder_unittest.cc",
     "rationalization_util_unittest.cc",
+    "strike_database_integrator_test_strike_database_unittest.cc",
+    "strike_database_unittest.cc",
     "test_utils/test_profiles.cc",
     "test_utils/test_profiles.h",
     "ui/address_combobox_model_unittest.cc",
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.cc b/components/autofill/core/browser/payments/credit_card_save_manager.cc
index 2965176..aa9cda76 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.cc
@@ -35,8 +35,8 @@
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/validation.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
diff --git a/components/autofill/core/browser/payments/credit_card_save_strike_database.h b/components/autofill/core/browser/payments/credit_card_save_strike_database.h
index 6ce21bee..cb228fd5 100644
--- a/components/autofill/core/browser/payments/credit_card_save_strike_database.h
+++ b/components/autofill/core/browser/payments/credit_card_save_strike_database.h
@@ -8,8 +8,8 @@
 #include <stdint.h>
 #include <string>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
-#include "components/autofill/core/browser/payments/strike_database_integrator_base.h"
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
 
 namespace autofill {
 
@@ -17,7 +17,7 @@
 // local and upload).
 class CreditCardSaveStrikeDatabase : public StrikeDatabaseIntegratorBase {
  public:
-  CreditCardSaveStrikeDatabase(StrikeDatabase* strike_database);
+  explicit CreditCardSaveStrikeDatabase(StrikeDatabase* strike_database);
   ~CreditCardSaveStrikeDatabase() override;
 
   std::string GetProjectPrefix() override;
diff --git a/components/autofill/core/browser/payments/fido_authentication_strike_database.h b/components/autofill/core/browser/payments/fido_authentication_strike_database.h
index 5ee994e..637feec 100644
--- a/components/autofill/core/browser/payments/fido_authentication_strike_database.h
+++ b/components/autofill/core/browser/payments/fido_authentication_strike_database.h
@@ -8,8 +8,8 @@
 #include <stdint.h>
 #include <string>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
-#include "components/autofill/core/browser/payments/strike_database_integrator_base.h"
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
 
 namespace autofill {
 
@@ -17,7 +17,7 @@
 // authentication for card unmasking.
 class FidoAuthenticationStrikeDatabase : public StrikeDatabaseIntegratorBase {
  public:
-  FidoAuthenticationStrikeDatabase(StrikeDatabase* strike_database);
+  explicit FidoAuthenticationStrikeDatabase(StrikeDatabase* strike_database);
   ~FidoAuthenticationStrikeDatabase() override;
 
   // Strikes to add when user declines opt-in offer.
diff --git a/components/autofill/core/browser/payments/local_card_migration_strike_database.h b/components/autofill/core/browser/payments/local_card_migration_strike_database.h
index 5f732c0..f6f2ec5 100644
--- a/components/autofill/core/browser/payments/local_card_migration_strike_database.h
+++ b/components/autofill/core/browser/payments/local_card_migration_strike_database.h
@@ -8,15 +8,15 @@
 #include <stdint.h>
 #include <string>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
-#include "components/autofill/core/browser/payments/strike_database_integrator_base.h"
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
 
 namespace autofill {
 
 // Implementation of StrikeDatabaseIntegratorBase for local card migrations.
 class LocalCardMigrationStrikeDatabase : public StrikeDatabaseIntegratorBase {
  public:
-  LocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
+  explicit LocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
   ~LocalCardMigrationStrikeDatabase() override;
 
   // Strikes to remove when user adds new local card.
diff --git a/components/autofill/core/browser/payments/test_strike_database.h b/components/autofill/core/browser/payments/test_strike_database.h
index 7fb5fb5..61adfa3 100644
--- a/components/autofill/core/browser/payments/test_strike_database.h
+++ b/components/autofill/core/browser/payments/test_strike_database.h
@@ -11,8 +11,8 @@
 #include <utility>
 #include <vector>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/proto/strike_data.pb.h"
+#include "components/autofill/core/browser/strike_database.h"
 
 namespace autofill {
 
diff --git a/components/autofill/core/browser/payments/strike_database.cc b/components/autofill/core/browser/strike_database.cc
similarity index 98%
rename from components/autofill/core/browser/payments/strike_database.cc
rename to components/autofill/core/browser/strike_database.cc
index 69e80fe..1221d781 100644
--- a/components/autofill/core/browser/payments/strike_database.cc
+++ b/components/autofill/core/browser/strike_database.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 
 #include <algorithm>
 #include <string>
@@ -49,7 +49,7 @@
 StrikeDatabase::~StrikeDatabase() = default;
 
 int StrikeDatabase::AddStrikes(int strikes_increase, const std::string& key) {
-  DCHECK(strikes_increase > 0);
+  DCHECK_GT(strikes_increase, 0);
   int num_strikes =
       strike_map_cache_.count(key)  // Cache has entry for |key|.
           ? strike_map_cache_[key].num_strikes() + strikes_increase
diff --git a/components/autofill/core/browser/payments/strike_database.h b/components/autofill/core/browser/strike_database.h
similarity index 96%
rename from components/autofill/core/browser/payments/strike_database.h
rename to components/autofill/core/browser/strike_database.h
index d28f29b5..e0fecaa 100644
--- a/components/autofill/core/browser/payments/strike_database.h
+++ b/components/autofill/core/browser/strike_database.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_H_
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_H_
 
 #include <map>
 #include <memory>
@@ -181,4 +181,4 @@
 
 }  // namespace autofill
 
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_H_
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/payments/strike_database_integrator_base.cc b/components/autofill/core/browser/strike_database_integrator_base.cc
similarity index 97%
rename from components/autofill/core/browser/payments/strike_database_integrator_base.cc
rename to components/autofill/core/browser/strike_database_integrator_base.cc
index 30f9404..22226d8c 100644
--- a/components/autofill/core/browser/payments/strike_database_integrator_base.cc
+++ b/components/autofill/core/browser/strike_database_integrator_base.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/payments/strike_database_integrator_base.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
 
 #include <algorithm>
 #include <string>
diff --git a/components/autofill/core/browser/payments/strike_database_integrator_base.h b/components/autofill/core/browser/strike_database_integrator_base.h
similarity index 91%
rename from components/autofill/core/browser/payments/strike_database_integrator_base.h
rename to components/autofill/core/browser/strike_database_integrator_base.h
index 60538ea..8e9296e3 100644
--- a/components/autofill/core/browser/payments/strike_database_integrator_base.h
+++ b/components/autofill/core/browser/strike_database_integrator_base.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_BASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_BASE_H_
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_BASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_BASE_H_
 
 #include <stdint.h>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 
 namespace autofill {
 
@@ -21,7 +21,7 @@
 // be loaded once per browser session.
 class StrikeDatabaseIntegratorBase {
  public:
-  StrikeDatabaseIntegratorBase(StrikeDatabase* strike_database);
+  explicit StrikeDatabaseIntegratorBase(StrikeDatabase* strike_database);
   virtual ~StrikeDatabaseIntegratorBase();
 
   // Returns whether or not strike count for |id| has reached the strike limit
@@ -109,4 +109,4 @@
 
 }  // namespace autofill
 
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_BASE_H_
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_BASE_H_
diff --git a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.cc b/components/autofill/core/browser/strike_database_integrator_test_strike_database.cc
similarity index 93%
rename from components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.cc
rename to components/autofill/core/browser/strike_database_integrator_test_strike_database.cc
index 6cb36f3..c9bc6be 100644
--- a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.cc
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_test_strike_database.h"
 
 #include "components/autofill/core/browser/proto/strike_data.pb.h"
 
diff --git a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.h b/components/autofill/core/browser/strike_database_integrator_test_strike_database.h
similarity index 71%
rename from components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.h
rename to components/autofill/core/browser/strike_database_integrator_test_strike_database.h
index c26e36af..2a44cad 100644
--- a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.h
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
 
 #include <stdint.h>
 #include <string>
 
-#include "components/autofill/core/browser/payments/strike_database.h"
-#include "components/autofill/core/browser/payments/strike_database_integrator_base.h"
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
 
 namespace autofill {
 
@@ -41,4 +41,4 @@
 
 }  // namespace autofill
 
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database_unittest.cc b/components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc
similarity index 98%
rename from components/autofill/core/browser/payments/strike_database_integrator_test_strike_database_unittest.cc
rename to components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc
index e87e7df4..a56f448 100644
--- a/components/autofill/core/browser/payments/strike_database_integrator_test_strike_database_unittest.cc
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/payments/strike_database_integrator_test_strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_test_strike_database.h"
 
 #include <utility>
 #include <vector>
@@ -23,7 +23,7 @@
 
 class StrikeDatabaseIntegratorTestStrikeDatabaseTest : public ::testing::Test {
  public:
-  StrikeDatabaseIntegratorTestStrikeDatabaseTest() {}
+  StrikeDatabaseIntegratorTestStrikeDatabaseTest() = default;
 
   void SetUp() override {
     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
diff --git a/components/autofill/core/browser/payments/strike_database_unittest.cc b/components/autofill/core/browser/strike_database_unittest.cc
similarity index 99%
rename from components/autofill/core/browser/payments/strike_database_unittest.cc
rename to components/autofill/core/browser/strike_database_unittest.cc
index ddbda726..872746a 100644
--- a/components/autofill/core/browser/payments/strike_database_unittest.cc
+++ b/components/autofill/core/browser/strike_database_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 
 #include <utility>
 #include <vector>
diff --git a/components/autofill_assistant/browser/fake_starter_platform_delegate.cc b/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
index 3ae3df5a..233ee49 100644
--- a/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
+++ b/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
@@ -29,6 +29,14 @@
   }
 }
 
+bool FakeStarterPlatformDelegate::IsRegularScriptRunning() const {
+  return is_regular_script_running_;
+}
+
+bool FakeStarterPlatformDelegate::IsRegularScriptVisible() const {
+  return is_regular_script_visible_;
+}
+
 WebsiteLoginManager* FakeStarterPlatformDelegate::GetWebsiteLoginManager()
     const {
   return website_login_manager_;
diff --git a/components/autofill_assistant/browser/fake_starter_platform_delegate.h b/components/autofill_assistant/browser/fake_starter_platform_delegate.h
index 472f03e..9a1f4c6 100644
--- a/components/autofill_assistant/browser/fake_starter_platform_delegate.h
+++ b/components/autofill_assistant/browser/fake_starter_platform_delegate.h
@@ -26,6 +26,8 @@
       GURL url,
       std::unique_ptr<TriggerContext> trigger_context,
       const base::Optional<TriggerScriptProto>& trigger_script) override;
+  bool IsRegularScriptRunning() const override;
+  bool IsRegularScriptVisible() const override;
   WebsiteLoginManager* GetWebsiteLoginManager() const override;
   version_info::Channel GetChannel() const override;
   bool GetFeatureModuleInstalled() const override;
@@ -72,6 +74,8 @@
       std::unique_ptr<TriggerContext> trigger_context,
       const base::Optional<TriggerScriptProto>& trigger_script)>
       start_regular_script_callback_;
+  bool is_regular_script_running_ = false;
+  bool is_regular_script_visible_ = false;
 
   int num_install_feature_module_called_ = 0;
   int num_show_onboarding_called_ = 0;
diff --git a/components/autofill_assistant/browser/metrics.cc b/components/autofill_assistant/browser/metrics.cc
index 0d19e93..4bb1419 100644
--- a/components/autofill_assistant/browser/metrics.cc
+++ b/components/autofill_assistant/browser/metrics.cc
@@ -150,7 +150,16 @@
 
 // static
 void Metrics::RecordLiteScriptStarted(ukm::UkmRecorder* ukm_recorder,
-                                      content::WebContents* web_contents,
+                                      ukm::SourceId source_id,
+                                      LiteScriptStarted event) {
+  ukm::builders::AutofillAssistant_LiteScriptStarted(source_id)
+      .SetLiteScriptStarted(static_cast<int64_t>(event))
+      .Record(ukm_recorder);
+}
+
+// static
+void Metrics::RecordLiteScriptStarted(ukm::UkmRecorder* ukm_recorder,
+                                      ukm::SourceId source_id,
                                       StartupUtil::StartupMode startup_mode,
                                       bool feature_module_installed,
                                       bool is_first_time_user) {
@@ -185,19 +194,15 @@
       return;
   }
 
-  ukm::builders::AutofillAssistant_LiteScriptStarted(
-      ukm::GetSourceIdForWebContentsDocument(web_contents))
-      .SetLiteScriptStarted(static_cast<int64_t>(event))
-      .Record(ukm_recorder);
+  RecordLiteScriptStarted(ukm_recorder, source_id, event);
 }
 
 // static
 void Metrics::RecordLiteScriptFinished(ukm::UkmRecorder* ukm_recorder,
-                                       content::WebContents* web_contents,
+                                       ukm::SourceId source_id,
                                        TriggerUIType trigger_ui_type,
                                        LiteScriptFinishedState event) {
-  ukm::builders::AutofillAssistant_LiteScriptFinished(
-      ukm::GetSourceIdForWebContentsDocument(web_contents))
+  ukm::builders::AutofillAssistant_LiteScriptFinished(source_id)
       .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type))
       .SetLiteScriptFinished(static_cast<int64_t>(event))
       .Record(ukm_recorder);
@@ -205,11 +210,10 @@
 
 // static
 void Metrics::RecordLiteScriptShownToUser(ukm::UkmRecorder* ukm_recorder,
-                                          content::WebContents* web_contents,
+                                          ukm::SourceId source_id,
                                           TriggerUIType trigger_ui_type,
                                           LiteScriptShownToUser event) {
-  ukm::builders::AutofillAssistant_LiteScriptShownToUser(
-      ukm::GetSourceIdForWebContentsDocument(web_contents))
+  ukm::builders::AutofillAssistant_LiteScriptShownToUser(source_id)
       .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type))
       .SetLiteScriptShownToUser(static_cast<int64_t>(event))
       .Record(ukm_recorder);
@@ -217,11 +221,10 @@
 
 // static
 void Metrics::RecordLiteScriptOnboarding(ukm::UkmRecorder* ukm_recorder,
-                                         content::WebContents* web_contents,
+                                         ukm::SourceId source_id,
                                          TriggerUIType trigger_ui_type,
                                          LiteScriptOnboarding event) {
-  ukm::builders::AutofillAssistant_LiteScriptOnboarding(
-      ukm::GetSourceIdForWebContentsDocument(web_contents))
+  ukm::builders::AutofillAssistant_LiteScriptOnboarding(source_id)
       .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type))
       .SetLiteScriptOnboarding(static_cast<int64_t>(event))
       .Record(ukm_recorder);
diff --git a/components/autofill_assistant/browser/metrics.h b/components/autofill_assistant/browser/metrics.h
index ab4dbc5..31b3b69 100644
--- a/components/autofill_assistant/browser/metrics.h
+++ b/components/autofill_assistant/browser/metrics.h
@@ -8,7 +8,6 @@
 #include <ostream>
 #include "components/autofill_assistant/browser/service.pb.h"
 #include "components/autofill_assistant/browser/startup_util.h"
-#include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace autofill_assistant {
@@ -214,6 +213,10 @@
     LITE_SCRIPT_NO_INITIAL_URL = 8,
     // Since Chrome M-91. A mandatory script parameter was missing.
     LITE_SCRIPT_MANDATORY_PARAMETER_MISSING = 9,
+    // Since Chrome M-92. The user never navigated to a different domain.
+    LITE_SCRIPT_NAVIGATED_AWAY = 10,
+    // Since Chrome M-92. The navigation to the target domain failed.
+    LITE_SCRIPT_NAVIGATION_ERROR = 11,
 
     // DEPRECATED, only sent by Chrome M-86 and M-87.
     //
@@ -223,7 +226,7 @@
     // User has rejected the onboarding and thus opted out of the experience.
     LITE_SCRIPT_ONBOARDING_REJECTED = 2,
 
-    kMaxValue = LITE_SCRIPT_MANDATORY_PARAMETER_MISSING
+    kMaxValue = LITE_SCRIPT_NAVIGATION_ERROR
   };
 
   // The different ways in which a lite script may finish.
@@ -351,20 +354,23 @@
                                                       bool initially_right,
                                                       bool success);
   static void RecordLiteScriptStarted(ukm::UkmRecorder* ukm_recorder,
-                                      content::WebContents* web_contents,
+                                      ukm::SourceId source_id,
+                                      LiteScriptStarted event);
+  static void RecordLiteScriptStarted(ukm::UkmRecorder* ukm_recorder,
+                                      ukm::SourceId source_id,
                                       StartupUtil::StartupMode startup_mode,
                                       bool feature_module_installed,
                                       bool is_first_time_user);
   static void RecordLiteScriptFinished(ukm::UkmRecorder* ukm_recorder,
-                                       content::WebContents* web_contents,
+                                       ukm::SourceId source_id,
                                        TriggerUIType trigger_ui_type,
                                        LiteScriptFinishedState event);
   static void RecordLiteScriptShownToUser(ukm::UkmRecorder* ukm_recorder,
-                                          content::WebContents* web_contents,
+                                          ukm::SourceId source_id,
                                           TriggerUIType trigger_ui_type,
                                           LiteScriptShownToUser event);
   static void RecordLiteScriptOnboarding(ukm::UkmRecorder* ukm_recorder,
-                                         content::WebContents* web_contents,
+                                         ukm::SourceId source_id,
                                          TriggerUIType trigger_ui_type,
                                          LiteScriptOnboarding event);
   static void RecordOnboardingResult(OnBoarding event);
diff --git a/components/autofill_assistant/browser/starter.cc b/components/autofill_assistant/browser/starter.cc
index 5fccde24..cdf4645c 100644
--- a/components/autofill_assistant/browser/starter.cc
+++ b/components/autofill_assistant/browser/starter.cc
@@ -26,6 +26,7 @@
 #include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h"
 #include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h"
 #include "components/autofill_assistant/browser/url_utils.h"
+#include "components/ukm/content/source_url_recorder.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
@@ -69,6 +70,14 @@
       /* disable_auth_if_no_access_token = */ true);
 }
 
+// Returns whether |trigger_context| contains either the REQUEST_TRIGGER_SCRIPT
+// or the TRIGGER_SCRIPTS_BASE64 script parameter.
+bool IsTriggerScriptContext(const TriggerContext& trigger_context) {
+  const auto& script_parameters = trigger_context.GetScriptParameters();
+  return script_parameters.GetRequestsTriggerScript() ||
+         script_parameters.GetBase64TriggerScriptsResponseProto();
+}
+
 // The heuristic is shared across all instances and initialized on first use. As
 // such, we do not support updating the heuristic while Chrome is running.
 const scoped_refptr<StarterHeuristic> GetOrCreateStarterHeuristic() {
@@ -85,6 +94,7 @@
                  ukm::UkmRecorder* ukm_recorder,
                  base::WeakPtr<RuntimeManagerImpl> runtime_manager)
     : content::WebContentsObserver(web_contents),
+      next_ukm_source_id_(ukm::GetSourceIdForWebContentsDocument(web_contents)),
       platform_delegate_(platform_delegate),
       ukm_recorder_(ukm_recorder),
       runtime_manager_(runtime_manager),
@@ -94,35 +104,75 @@
 
 Starter::~Starter() = default;
 
+void Starter::DidStartNavigation(content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame()) {
+    return;
+  }
+  next_ukm_source_id_ = navigation_handle->GetNextPageUkmSourceId();
+}
+
 void Starter::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  // User-initiated navigations during non-trigger-script startups will cancel
-  // the startup. This is mostly intended for navigations while the onboarding
-  // is being shown.
-  if (IsStartupPending() && !navigation_handle->WasServerRedirect() &&
-      !trigger_script_coordinator_ &&
-      navigation_handle->GetURL() !=
-          StartupUtil().ChooseStartupUrlForIntent(*pending_trigger_context_)) {
-    Metrics::RecordDropOut(
-        waiting_for_onboarding_ ? Metrics::DropOutReason::ONBOARDING_NAVIGATION
-                                : Metrics::DropOutReason::NAVIGATION,
-        pending_trigger_context_->GetScriptParameters().GetIntent().value_or(
-            std::string()));
-    CancelPendingStartup(
-        Metrics::LiteScriptFinishedState::LITE_SCRIPT_CANCELED);
-  }
-
-  if (!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage() ||
-      navigation_handle->WasServerRedirect()) {
+  if (!navigation_handle->IsInMainFrame()) {
     return;
   }
 
-  MaybeStartImplicitlyForUrl(navigation_handle->GetURL());
+  // Navigating away from the deeplink domain during startup OR ending up on an
+  // error page will break the flow, unless a trigger script is currently
+  // running (in which case, the trigger script will handle this event).
+  if (IsStartupPending() && navigation_handle->HasCommitted() &&
+      !trigger_script_coordinator_) {
+    bool navigated_to_target_domain = url_utils::IsSamePublicSuffixDomain(
+        navigation_handle->GetURL(),
+        StartupUtil()
+            .ChooseStartupUrlForIntent(*GetPendingTriggerContext())
+            .value_or(GURL()));
+
+    if (navigated_to_target_domain) {
+      if (waiting_for_deeplink_navigation_) {
+        Start(std::move(pending_trigger_context_));
+      }
+      // Ignore; navigations to the target domain during startup are allowed.
+      return;
+    }
+
+    if (waiting_for_deeplink_navigation_) {
+      if (navigated_to_target_domain) {
+        Start(std::move(pending_trigger_context_));
+        return;
+      }
+      // Note: this will record for the current domain, not the target domain.
+      // There seems to be no way to avoid this.
+      Metrics::RecordLiteScriptStarted(
+          ukm_recorder_, next_ukm_source_id_,
+          navigation_handle->IsErrorPage()
+              ? Metrics::LiteScriptStarted::LITE_SCRIPT_NAVIGATION_ERROR
+              : Metrics::LiteScriptStarted::LITE_SCRIPT_NAVIGATED_AWAY);
+      CancelPendingStartup(base::nullopt);
+    } else {
+      // Regular startup was interrupted (most likely during the onboarding).
+      Metrics::RecordDropOut(waiting_for_onboarding_
+                                 ? Metrics::DropOutReason::ONBOARDING_NAVIGATION
+                                 : Metrics::DropOutReason::NAVIGATION,
+                             GetPendingTriggerContext()
+                                 ->GetScriptParameters()
+                                 .GetIntent()
+                                 .value_or(std::string()));
+      CancelPendingStartup(base::nullopt);
+    }
+    // Note: do not early-return here. While the previous startup has failed, we
+    // may have navigated to a new supported domain and may need to start
+    // implicitly.
+  }
+
+  if (navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage()) {
+    MaybeStartImplicitlyForUrl(navigation_handle->GetURL());
+  }
 }
 
 void Starter::MaybeStartImplicitlyForUrl(const GURL& url) {
   if (!fetch_trigger_scripts_on_navigation_ || IsStartupPending() ||
-      !url.is_valid()) {
+      platform_delegate_->IsRegularScriptRunning() || !url.is_valid()) {
     return;
   }
 
@@ -154,8 +204,14 @@
 }
 
 bool Starter::IsStartupPending() const {
-  return pending_trigger_context_ != nullptr ||
-         trigger_script_coordinator_ != nullptr;
+  return GetPendingTriggerContext() != nullptr;
+}
+
+TriggerContext* Starter::GetPendingTriggerContext() const {
+  if (trigger_script_coordinator_) {
+    return &trigger_script_coordinator_->GetTriggerContext();
+  }
+  return pending_trigger_context_.get();
 }
 
 void Starter::OnTabInteractabilityChanged(bool is_interactable) {
@@ -186,9 +242,7 @@
   // allowing the startup to proceed. If not, cancel the startup.
   if (IsStartupPending()) {
     StartupMode startup_mode = StartupUtil().ChooseStartupModeForIntent(
-        trigger_script_coordinator_ != nullptr
-            ? trigger_script_coordinator_->GetTriggerContext()
-            : *pending_trigger_context_,
+        *GetPendingTriggerContext(),
         {msbb_setting_enabled, proactive_help_setting_enabled,
          feature_module_installed});
     switch (startup_mode) {
@@ -236,14 +290,33 @@
        platform_delegate_->GetProactiveHelpSettingEnabled(),
        platform_delegate_->GetFeatureModuleInstalled()});
 
+  // Trigger scripts may need to wait for navigation to the deeplink domain to
+  // ensure that UKMs are recorded for the right source-id.
+  auto startup_url =
+      StartupUtil().ChooseStartupUrlForIntent(*pending_trigger_context_);
+  if (IsTriggerScriptContext(*pending_trigger_context_) &&
+      !startup_url.has_value()) {
+    // Fail immediately if there is no deeplink domain to wait for.
+    // Note: this will record the impression for the current domain.
+    Metrics::RecordLiteScriptStarted(
+        ukm_recorder_, next_ukm_source_id_,
+        Metrics::LiteScriptStarted::LITE_SCRIPT_NO_INITIAL_URL);
+    OnStartDone(/* start_regular_script = */ false);
+    return;
+  }
+  if (IsTriggerScriptContext(*pending_trigger_context_) &&
+      !url_utils::IsSamePublicSuffixDomain(
+          web_contents()->GetLastCommittedURL(),
+          startup_url.value_or(GURL()))) {
+    waiting_for_deeplink_navigation_ = true;
+    return;
+  }
+
   // Record startup metrics for trigger scripts as soon as possible to establish
   // a baseline.
-  const auto& script_parameters =
-      pending_trigger_context_->GetScriptParameters();
-  if (script_parameters.GetRequestsTriggerScript() ||
-      script_parameters.GetBase64TriggerScriptsResponseProto()) {
+  if (IsTriggerScriptContext(*pending_trigger_context_)) {
     Metrics::RecordLiteScriptStarted(
-        ukm_recorder_, web_contents(), startup_mode,
+        ukm_recorder_, next_ukm_source_id_, startup_mode,
         platform_delegate_->GetFeatureModuleInstalled(),
         platform_delegate_->GetIsFirstTimeUser());
   }
@@ -263,7 +336,8 @@
   }
 }
 
-void Starter::CancelPendingStartup(Metrics::LiteScriptFinishedState state) {
+void Starter::CancelPendingStartup(
+    base::Optional<Metrics::LiteScriptFinishedState> state) {
   if (!IsStartupPending()) {
     return;
   }
@@ -274,8 +348,8 @@
     waiting_for_onboarding_ = false;
   }
   OnStartDone(/* start_regular_script = */ false);
-  if (trigger_script_coordinator_) {
-    trigger_script_coordinator_->Stop(state);
+  if (trigger_script_coordinator_ && state) {
+    trigger_script_coordinator_->Stop(*state);
   }
   trigger_script_coordinator_.reset();
   pending_trigger_context_.reset();
@@ -342,7 +416,7 @@
           script_parameters.GetBase64TriggerScriptsResponseProto().value());
       if (!service_request_sender) {
         Metrics::RecordLiteScriptFinished(
-            ukm_recorder_, web_contents(), UNSPECIFIED_TRIGGER_UI_TYPE,
+            ukm_recorder_, next_ukm_source_id_, UNSPECIFIED_TRIGGER_UI_TYPE,
             Metrics::LiteScriptFinishedState::
                 LITE_SCRIPT_BASE64_DECODING_ERROR);
         OnTriggerScriptFinished(
@@ -373,7 +447,8 @@
       url_fetcher.GetTriggerScriptsEndpoint(),
       std::make_unique<StaticTriggerConditions>(
           platform_delegate_, pending_trigger_context_.get(), startup_url),
-      std::make_unique<DynamicTriggerConditions>(), ukm_recorder_);
+      std::make_unique<DynamicTriggerConditions>(), ukm_recorder_,
+      next_ukm_source_id_);
 
   // Note: for the duration of the trigger script, the trigger script
   // coordinator will take ownership of the pending trigger context.
@@ -426,7 +501,7 @@
   runtime_manager_->SetUIState(UIState::kShown);
   waiting_for_onboarding_ = true;
   platform_delegate_->ShowOnboarding(
-      /* use_dialog_onboarding = */ false, *pending_trigger_context_,
+      /* use_dialog_onboarding = */ false, *GetPendingTriggerContext(),
       base::BindOnce(&Starter::OnOnboardingFinished,
                      weak_ptr_factory_.GetWeakPtr(), trigger_script));
 }
@@ -437,7 +512,7 @@
     OnboardingResult result) {
   waiting_for_onboarding_ = false;
   auto intent =
-      pending_trigger_context_->GetScriptParameters().GetIntent().value_or(
+      GetPendingTriggerContext()->GetScriptParameters().GetIntent().value_or(
           std::string());
   switch (result) {
     case OnboardingResult::DISMISSED:
@@ -476,9 +551,11 @@
 void Starter::OnStartDone(bool start_regular_script,
                           base::Optional<TriggerScriptProto> trigger_script) {
   if (!start_regular_script) {
-    // Catch-all to ensure that after a failed startup attempt we no longer
-    // register as visible to runtime observers.
-    runtime_manager_->SetUIState(UIState::kNotShown);
+    // Catch-all to ensure that after a failed startup attempt we reset the
+    // UI state.
+    runtime_manager_->SetUIState(platform_delegate_->IsRegularScriptVisible()
+                                     ? UIState::kShown
+                                     : UIState::kNotShown);
     pending_trigger_context_.reset();
     return;
   }
diff --git a/components/autofill_assistant/browser/starter.h b/components/autofill_assistant/browser/starter.h
index ba51a96..d788a36 100644
--- a/components/autofill_assistant/browser/starter.h
+++ b/components/autofill_assistant/browser/starter.h
@@ -50,6 +50,8 @@
   void Start(std::unique_ptr<TriggerContext> trigger_context);
 
   // content::WebContentsObserver:
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
 
@@ -69,7 +71,8 @@
   // is currently running, this will record |state| as the reason for stopping.
   // This will also hide any currently shown UI (such as a trigger script or the
   // onboarding).
-  void CancelPendingStartup(Metrics::LiteScriptFinishedState state);
+  void CancelPendingStartup(
+      base::Optional<Metrics::LiteScriptFinishedState> state);
 
   // Installs the feature module if necessary, otherwise directly invokes
   // |OnFeatureModuleInstalled|.
@@ -115,7 +118,19 @@
 
   void DeleteTriggerScriptCoordinator();
 
+  // Returns a pointer to the currently pending trigger context, or nullptr.
+  // Use this method instead of directly accessing |pending_trigger_context_| in
+  // cases where the context could be temporarily owned by
+  // |trigger_script_coordinator_|.
+  TriggerContext* GetPendingTriggerContext() const;
+
+  // The UKM source id to use for UKM metrics. This usually points to the last
+  // committed URL, except during navigations, in which case it will point to
+  // the source id that the finished navigation will eventually have.
+  ukm::SourceId next_ukm_source_id_ = ukm::kInvalidSourceId;
+
   bool waiting_for_onboarding_ = false;
+  bool waiting_for_deeplink_navigation_ = false;
   bool is_custom_tab_ = false;
   StarterPlatformDelegate* platform_delegate_ = nullptr;
   ukm::UkmRecorder* ukm_recorder_ = nullptr;
diff --git a/components/autofill_assistant/browser/starter_platform_delegate.h b/components/autofill_assistant/browser/starter_platform_delegate.h
index d5003ac7..842839f 100644
--- a/components/autofill_assistant/browser/starter_platform_delegate.h
+++ b/components/autofill_assistant/browser/starter_platform_delegate.h
@@ -36,6 +36,10 @@
       GURL url,
       std::unique_ptr<TriggerContext> trigger_context,
       const base::Optional<TriggerScriptProto>& trigger_script) = 0;
+  // Returns whether a regular script is currently running.
+  virtual bool IsRegularScriptRunning() const;
+  // Returns whether a regular script is currently showing UI to the user.
+  virtual bool IsRegularScriptVisible() const;
 
   // Access to the login manager.
   virtual WebsiteLoginManager* GetWebsiteLoginManager() const = 0;
diff --git a/components/autofill_assistant/browser/starter_unittest.cc b/components/autofill_assistant/browser/starter_unittest.cc
index 01509019..38d800b1 100644
--- a/components/autofill_assistant/browser/starter_unittest.cc
+++ b/components/autofill_assistant/browser/starter_unittest.cc
@@ -173,18 +173,17 @@
     return RecordedUkmMetric("AutofillAssistant.LiteScriptOnboarding");
   }
 
-  // Simulates a redirect-navigation to |redirect_url|, followed by a regular
-  // navigation to |url|.
-  void SimulateRedirectToUrl(const GURL& url, const GURL& redirect_url) {
+  // Simulates a navigation from the last committed URL to urls[size-1] along
+  // the intermediate redirect-hops in |urls|.
+  void SimulateRedirectToUrl(const std::vector<GURL>& urls) {
     std::unique_ptr<content::NavigationSimulator> simulator =
         content::NavigationSimulator::CreateRendererInitiated(
-            GURL(url), web_contents()->GetMainFrame());
+            web_contents()->GetLastCommittedURL(),
+            web_contents()->GetMainFrame());
     simulator->Start();
-    simulator->Redirect(redirect_url);
-    simulator->Commit();
-    simulator = content::NavigationSimulator::CreateRendererInitiated(
-        GURL(url), web_contents()->GetMainFrame());
-    simulator->Start();
+    for (const auto& url : urls) {
+      simulator->Redirect(url);
+    }
     simulator->Commit();
   }
 
@@ -305,7 +304,7 @@
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", "abc"}};
   TriggerContext::Options options;
-  options.initial_url = "https://www.example.com";
+  options.initial_url = kExampleDeeplink;
   auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
   scoped_feature_list->InitAndDisableFeature(
       features::kAutofillAssistantProactiveHelp);
@@ -329,7 +328,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
   EXPECT_CALL(mock_start_regular_script_callback_,
@@ -362,7 +361,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
   EXPECT_CALL(mock_start_regular_script_callback_,
@@ -460,7 +459,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
@@ -490,7 +489,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink},
       {"INTENT", "SHOPPING_ASSISTED_CHECKOUT"}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
@@ -519,7 +518,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"REQUEST_TRIGGER_SCRIPT", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
   EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
       .Times(0);
@@ -546,7 +545,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"REQUEST_TRIGGER_SCRIPT", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
   EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
       .Times(0);
@@ -573,7 +572,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"REQUEST_TRIGGER_SCRIPT", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
   options.onboarding_shown = false;
@@ -593,7 +592,7 @@
             // ORIGINAL_DEEPLINK, not for the |initial_url|.
             GetTriggerScriptsRequestProto request;
             ASSERT_TRUE(request.ParseFromString(request_body));
-            EXPECT_THAT(request.url(), Eq(GURL("https://www.example.com")));
+            EXPECT_THAT(request.url(), Eq(GURL(kExampleDeeplink)));
             std::move(callback).Run(net::HTTP_OK,
                                     CreateTriggerScriptResponseForTest());
           }));
@@ -631,7 +630,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", "#invalid_hashtag"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
 
@@ -661,7 +660,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
 
@@ -693,7 +692,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   TriggerContext::Options options;
   options.initial_url = "https://redirect.com/to/www/example/com";
   options.onboarding_shown = false;
@@ -735,7 +734,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
 
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
@@ -790,7 +789,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   starter_->Start(std::make_unique<TriggerContext>(
       std::make_unique<ScriptParameters>(script_parameters),
       TriggerContext::Options{}));
@@ -822,7 +821,7 @@
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "false"},
       {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
       .WillOnce([&]() {
         ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
@@ -839,18 +838,11 @@
 
   EXPECT_TRUE(UkmLiteScriptStarted(
       Metrics::LiteScriptStarted::LITE_SCRIPT_FIRST_TIME_USER));
-
-  content::WebContentsTester::For(web_contents())
-      ->NavigateAndCommit(GURL("https://www.different.com"));
-  // TODO(b/185476714): Fix lite script metrics to record for ORIGINAL_DEEPLINK
-  // instead of the current URL.
   EXPECT_TRUE(UkmLiteScriptFinished(
-      Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_FAILED_NAVIGATE,
-      GURL("https://www.different.com")));
+      Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_FAILED_NAVIGATE));
   EXPECT_TRUE(UkmLiteScriptOnboarding(
       Metrics::LiteScriptOnboarding::
-          LITE_SCRIPT_ONBOARDING_SEEN_AND_INTERRUPTED_BY_NAVIGATION,
-      GURL("https://www.different.com")));
+          LITE_SCRIPT_ONBOARDING_SEEN_AND_INTERRUPTED_BY_NAVIGATION));
   histogram_tester_.ExpectUniqueSample(
       "Android.AutofillAssistant.FeatureModuleInstallation",
       Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
@@ -868,22 +860,22 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
   starter_->Start(std::make_unique<TriggerContext>(
       std::make_unique<ScriptParameters>(script_parameters),
       TriggerContext::Options{}));
 
-  // Expect that the onboarding is not interrupted by a redirect to the
-  // ORIGINAL_DEEPLINK.
-  SimulateRedirectToUrl(GURL("https://www.example.com"),
-                        GURL("http://redirect.example.com"));
+  // Expect that the onboarding is not interrupted by a navigation to a
+  // subdomain of the ORIGINAL_DEEPLINK, nor by redirects along the way.
+  SimulateRedirectToUrl(
+      {GURL("http://redirect.com/example"), GURL("https://login.example.com")});
   histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
                                      0u);
 
-  // Redirecting to a different URL will cancel the onboarding.
-  SimulateRedirectToUrl(GURL("https://www.different.com"),
-                        GURL("http://redirect.example.com"));
+  // Navigating to a different domain will cancel the onboarding.
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://www.different.com"));
 
   EXPECT_FALSE(UkmLiteScriptStarted());
   EXPECT_FALSE(UkmLiteScriptFinished());
@@ -912,7 +904,7 @@
   std::map<std::string, std::string> script_parameters = {
       {"ENABLED", "true"},
       {"START_IMMEDIATELY", "true"},
-      {"ORIGINAL_DEEPLINK", "https://www.example.com"}};
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
   starter_->Start(std::make_unique<TriggerContext>(
       std::make_unique<ScriptParameters>(script_parameters),
@@ -1033,6 +1025,218 @@
                                      0u);
 }
 
+TEST_F(StarterTest, StartTriggerScriptBeforeRedirectRecordsUkmForTargetUrl) {
+  SetupPlatformDelegateForReturningUser();
+  fake_platform_delegate_.feature_module_installed_ = true;
+  fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
+  mock_trigger_script_service_request_sender_ = nullptr;
+
+  std::map<std::string, std::string> script_parameters = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "false"},
+      {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  TriggerContext::Options options;
+  options.initial_url = "https://redirect.com/to/www/example/com";
+
+  // Simulate a real flow that starts on some trigger site, which then redirects
+  // to the deeplink.
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://some-trigger-site.com"));
+
+  // Start the flow before the trigger site has had a chance to navigate to the
+  // target domain. This commonly happens due to android intent handling
+  // happening before navigations are started.
+  starter_->Start(std::make_unique<TriggerContext>(
+      std::make_unique<ScriptParameters>(script_parameters), options));
+
+  EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
+
+  std::unique_ptr<content::NavigationSimulator> simulator =
+      content::NavigationSimulator::CreateRendererInitiated(
+          web_contents()->GetLastCommittedURL(),
+          web_contents()->GetMainFrame());
+  simulator->Start();
+  simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
+  simulator->Redirect(GURL(kExampleDeeplink));
+  // To spice things up a bit more, we redirect to a subdomain of the target
+  // domain instead.
+  simulator->Redirect(GURL("https://signin.example.com"));
+  simulator->Commit();
+
+  EXPECT_TRUE(UkmLiteScriptStarted(
+      Metrics::LiteScriptStarted::LITE_SCRIPT_RETURNING_USER,
+      GURL("https://signin.example.com")));
+  EXPECT_FALSE(UkmLiteScriptFinished());
+  EXPECT_FALSE(UkmLiteScriptOnboarding());
+  histogram_tester_.ExpectUniqueSample(
+      "Android.AutofillAssistant.FeatureModuleInstallation",
+      Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
+  histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
+                                     0u);
+}
+
+TEST_F(StarterTest, RedirectFailsDuringPendingTriggerScriptStart) {
+  SetupPlatformDelegateForReturningUser();
+  fake_platform_delegate_.feature_module_installed_ = true;
+  fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
+  mock_trigger_script_service_request_sender_ = nullptr;
+
+  std::map<std::string, std::string> script_parameters = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "false"},
+      {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  TriggerContext::Options options;
+  options.initial_url = "https://redirect.com/to/www/example/com";
+
+  // Simulate a real flow that starts on some trigger site, which then redirects
+  // to the deeplink.
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://some-trigger-site.com"));
+
+  starter_->Start(std::make_unique<TriggerContext>(
+      std::make_unique<ScriptParameters>(script_parameters), options));
+
+  EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(0);
+
+  std::unique_ptr<content::NavigationSimulator> simulator =
+      content::NavigationSimulator::CreateRendererInitiated(
+          web_contents()->GetLastCommittedURL(),
+          web_contents()->GetMainFrame());
+  simulator->Start();
+  simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
+  simulator->Fail(net::ERR_BLOCKED_BY_CLIENT);
+  simulator->CommitErrorPage();
+
+  // Note that this impression is recorded for the last URL that a navigation-
+  // start event occurred for. We never reached the target domain, so this is
+  // unfortunately the best we can do.
+  EXPECT_TRUE(UkmLiteScriptStarted(
+      Metrics::LiteScriptStarted::LITE_SCRIPT_NAVIGATION_ERROR,
+      GURL("https://redirect.com/to/www/example/com")));
+  EXPECT_FALSE(UkmLiteScriptFinished());
+  EXPECT_FALSE(UkmLiteScriptOnboarding());
+  histogram_tester_.ExpectTotalCount(
+      "Android.AutofillAssistant.FeatureModuleInstallation", 0u);
+  histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
+                                     0u);
+}
+
+TEST_F(StarterTest, StartTriggerScriptDuringRedirectRecordsUkmForTargetUrl) {
+  SetupPlatformDelegateForReturningUser();
+  fake_platform_delegate_.feature_module_installed_ = true;
+  fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
+  mock_trigger_script_service_request_sender_ = nullptr;
+
+  std::map<std::string, std::string> script_parameters = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "false"},
+      {"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  TriggerContext::Options options;
+  options.initial_url = "https://redirect.com/to/www/example/com";
+
+  // Simulate a real flow that starts on some trigger site, which then redirects
+  // to the deeplink.
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://some-trigger-site.com"));
+
+  // Begin a navigation, then start the flow before the navigation is committed.
+  // UKM should still be recorded for the final URL, not the redirect URL.
+  EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
+  std::unique_ptr<content::NavigationSimulator> simulator =
+      content::NavigationSimulator::CreateRendererInitiated(
+          web_contents()->GetLastCommittedURL(),
+          web_contents()->GetMainFrame());
+  simulator->Start();
+  simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
+  starter_->Start(std::make_unique<TriggerContext>(
+      std::make_unique<ScriptParameters>(script_parameters), options));
+  simulator->Redirect(GURL(kExampleDeeplink));
+  simulator->Commit();
+
+  EXPECT_TRUE(UkmLiteScriptStarted(
+      Metrics::LiteScriptStarted::LITE_SCRIPT_RETURNING_USER,
+      GURL(kExampleDeeplink)));
+  EXPECT_FALSE(UkmLiteScriptFinished());
+  EXPECT_FALSE(UkmLiteScriptOnboarding());
+  histogram_tester_.ExpectUniqueSample(
+      "Android.AutofillAssistant.FeatureModuleInstallation",
+      Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
+  histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
+                                     0u);
+}
+
+TEST_F(StarterTest, RegularStartupDoesNotWaitForNavigationToFinish) {
+  SetupPlatformDelegateForReturningUser();
+  auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
+  std::map<std::string, std::string> script_parameters = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "true"},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  TriggerContext::Options options;
+
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://some-trigger-site.com"));
+  std::unique_ptr<content::NavigationSimulator> simulator =
+      content::NavigationSimulator::CreateRendererInitiated(
+          web_contents()->GetLastCommittedURL(),
+          web_contents()->GetMainFrame());
+  simulator->Start();
+  simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
+
+  {
+    EXPECT_CALL(mock_start_regular_script_callback_,
+                Run(GURL(kExampleDeeplink), _, _));
+    starter_->Start(std::make_unique<TriggerContext>(
+        std::make_unique<ScriptParameters>(script_parameters), options));
+  }
+
+  simulator->Redirect(GURL(kExampleDeeplink));
+  simulator->Commit();
+
+  EXPECT_FALSE(UkmLiteScriptStarted());
+  EXPECT_FALSE(UkmLiteScriptFinished());
+  EXPECT_FALSE(UkmLiteScriptOnboarding());
+  histogram_tester_.ExpectUniqueSample(
+      "Android.AutofillAssistant.FeatureModuleInstallation",
+      Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
+  histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
+                                      Metrics::OnBoarding::OB_ACCEPTED, 1u);
+  histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
+                                      Metrics::OnBoarding::OB_NOT_SHOWN, 1u);
+}
+
+TEST_F(StarterTest, DoNotStartImplicitlyIfAlreadyRunning) {
+  SetupPlatformDelegateForReturningUser();
+  fake_platform_delegate_.is_regular_script_running_ = true;
+  auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
+  scoped_feature_list->InitAndEnableFeature(
+      features::kAutofillAssistantInChromeTriggering);
+  starter_->CheckSettings();
+  std::map<std::string, std::string> script_parameters = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "true"},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  TriggerContext::Options options;
+
+  EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL("https://www.example.com/cart"));
+
+  task_environment()->RunUntilIdle();
+  EXPECT_FALSE(UkmLiteScriptStarted());
+  EXPECT_FALSE(UkmLiteScriptFinished());
+  EXPECT_FALSE(UkmLiteScriptOnboarding());
+  histogram_tester_.ExpectTotalCount(
+      "Android.AutofillAssistant.FeatureModuleInstallation", 0u);
+  histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
+                                     0u);
+  histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
+                                     0u);
+}
+
 TEST(MultipleStarterTest, HeuristicUsedByMultipleInstances) {
   content::BrowserTaskEnvironment task_environment;
   content::RenderViewHostTestEnabler rvh_test_enabler;
diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
index 5a25a2f..655b050 100644
--- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
+++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
@@ -39,7 +39,8 @@
     const GURL& get_trigger_scripts_server,
     std::unique_ptr<StaticTriggerConditions> static_trigger_conditions,
     std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions,
-    ukm::UkmRecorder* ukm_recorder)
+    ukm::UkmRecorder* ukm_recorder,
+    ukm::SourceId ukm_source_id)
     : content::WebContentsObserver(web_contents),
       starter_delegate_(starter_delegate),
       ui_delegate_(starter_delegate->CreateTriggerScriptUiDelegate()),
@@ -48,7 +49,8 @@
       web_controller_(std::move(web_controller)),
       static_trigger_conditions_(std::move(static_trigger_conditions)),
       dynamic_trigger_conditions_(std::move(dynamic_trigger_conditions)),
-      ukm_recorder_(ukm_recorder) {}
+      ukm_recorder_(ukm_recorder),
+      ukm_source_id_(ukm_source_id) {}
 
 TriggerScriptCoordinator::~TriggerScriptCoordinator() = default;
 
@@ -116,7 +118,7 @@
       initial_trigger_condition_evaluations_;
 
   Metrics::RecordLiteScriptShownToUser(
-      ukm_recorder_, web_contents(), UNSPECIFIED_TRIGGER_UI_TYPE,
+      ukm_recorder_, ukm_source_id_, UNSPECIFIED_TRIGGER_UI_TYPE,
       Metrics::LiteScriptShownToUser::LITE_SCRIPT_RUNNING);
   ui_delegate_->Attach(this);
   StartCheckingTriggerConditions();
@@ -128,7 +130,7 @@
     case TriggerScriptProto::NOT_NOW:
       if (visible_trigger_script_ != -1) {
         Metrics::RecordLiteScriptShownToUser(
-            ukm_recorder_, web_contents(), GetTriggerUiTypeForVisibleScript(),
+            ukm_recorder_, ukm_source_id_, GetTriggerUiTypeForVisibleScript(),
             Metrics::LiteScriptShownToUser::LITE_SCRIPT_NOT_NOW);
         trigger_scripts_[visible_trigger_script_]
             ->waiting_for_precondition_no_longer_true(true);
@@ -174,32 +176,32 @@
       switch (result) {
         case OnboardingResult::DISMISSED:
           Metrics::RecordLiteScriptOnboarding(
-              ukm_recorder_, web_contents(), trigger_ui_type,
+              ukm_recorder_, ukm_source_id_, trigger_ui_type,
               Metrics::LiteScriptOnboarding::
                   LITE_SCRIPT_ONBOARDING_SEEN_AND_DISMISSED);
           break;
         case OnboardingResult::REJECTED:
           Metrics::RecordLiteScriptOnboarding(
-              ukm_recorder_, web_contents(), trigger_ui_type,
+              ukm_recorder_, ukm_source_id_, trigger_ui_type,
               Metrics::LiteScriptOnboarding::
                   LITE_SCRIPT_ONBOARDING_SEEN_AND_REJECTED);
           break;
         case OnboardingResult::NAVIGATION:
           Metrics::RecordLiteScriptOnboarding(
-              ukm_recorder_, web_contents(), trigger_ui_type,
+              ukm_recorder_, ukm_source_id_, trigger_ui_type,
               Metrics::LiteScriptOnboarding::
                   LITE_SCRIPT_ONBOARDING_SEEN_AND_INTERRUPTED_BY_NAVIGATION);
           break;
         case OnboardingResult::ACCEPTED:
           Metrics::RecordLiteScriptOnboarding(
-              ukm_recorder_, web_contents(), trigger_ui_type,
+              ukm_recorder_, ukm_source_id_, trigger_ui_type,
               Metrics::LiteScriptOnboarding::
                   LITE_SCRIPT_ONBOARDING_SEEN_AND_ACCEPTED);
           break;
       }
     } else {
       Metrics::RecordLiteScriptOnboarding(
-          ukm_recorder_, web_contents(), trigger_ui_type,
+          ukm_recorder_, ukm_source_id_, trigger_ui_type,
           Metrics::LiteScriptOnboarding::
               LITE_SCRIPT_ONBOARDING_ALREADY_ACCEPTED);
     }
@@ -229,7 +231,7 @@
     return;
   }
   Metrics::RecordLiteScriptShownToUser(
-      ukm_recorder_, web_contents(), GetTriggerUiTypeForVisibleScript(),
+      ukm_recorder_, ukm_source_id_, GetTriggerUiTypeForVisibleScript(),
       Metrics::LiteScriptShownToUser::LITE_SCRIPT_SWIPE_DISMISSED);
   PerformTriggerScriptAction(trigger_scripts_[visible_trigger_script_]
                                  ->AsProto()
@@ -276,7 +278,7 @@
                                          LITE_SCRIPT_PROMPT_FAILED_NAVIGATE) {
     starter_delegate_->HideOnboarding();
     Metrics::RecordLiteScriptOnboarding(
-        ukm_recorder_, web_contents(), trigger_ui_type,
+        ukm_recorder_, ukm_source_id_, trigger_ui_type,
         Metrics::LiteScriptOnboarding::
             LITE_SCRIPT_ONBOARDING_SEEN_AND_INTERRUPTED_BY_NAVIGATION);
   }
@@ -308,7 +310,7 @@
 
   // The user has navigated away from the target domain. This will cancel the
   // current trigger script session.
-  if (!url_utils::IsInDomainOrSubDomain(GetCurrentURL(), deeplink_url_) &&
+  if (!url_utils::IsSamePublicSuffixDomain(GetCurrentURL(), deeplink_url_) &&
       !url_utils::IsInDomainOrSubDomain(GetCurrentURL(),
                                         additional_allowed_domains_)) {
 #ifndef NDEBUG
@@ -345,7 +347,7 @@
   OnEffectiveVisibilityChanged();
 }
 
-const TriggerContext& TriggerScriptCoordinator::GetTriggerContext() const {
+TriggerContext& TriggerScriptCoordinator::GetTriggerContext() const {
   return *trigger_context_;
 }
 
@@ -380,7 +382,7 @@
 void TriggerScriptCoordinator::WebContentsDestroyed() {
   if (!finished_state_recorded_) {
     Metrics::RecordLiteScriptFinished(
-        ukm_recorder_, web_contents(), GetTriggerUiTypeForVisibleScript(),
+        ukm_recorder_, ukm_source_id_, GetTriggerUiTypeForVisibleScript(),
         visible_trigger_script_ == -1
             ? Metrics::LiteScriptFinishedState::
                   LITE_SCRIPT_WEB_CONTENTS_DESTROYED_WHILE_INVISIBLE
@@ -426,7 +428,7 @@
   // set first thing.
 
   Metrics::RecordLiteScriptShownToUser(
-      ukm_recorder_, web_contents(), GetTriggerUiTypeForVisibleScript(),
+      ukm_recorder_, ukm_source_id_, GetTriggerUiTypeForVisibleScript(),
       Metrics::LiteScriptShownToUser::LITE_SCRIPT_SHOWN_TO_USER);
   ui_delegate_->ShowTriggerScript(
       trigger_scripts_[index]->AsProto().user_interface());
@@ -473,7 +475,7 @@
   if (visible_trigger_script_ != -1 &&
       !evaluated_trigger_conditions[visible_trigger_script_]) {
     Metrics::RecordLiteScriptShownToUser(
-        ukm_recorder_, web_contents(), GetTriggerUiTypeForVisibleScript(),
+        ukm_recorder_, ukm_source_id_, GetTriggerUiTypeForVisibleScript(),
         Metrics::LiteScriptShownToUser::
             LITE_SCRIPT_HIDE_ON_TRIGGER_CONDITION_NO_LONGER_TRUE);
     HideTriggerScript();
@@ -544,7 +546,7 @@
     const base::Optional<TriggerScriptProto>& trigger_script) {
   if (!finished_state_recorded_) {
     finished_state_recorded_ = true;
-    Metrics::RecordLiteScriptFinished(ukm_recorder_, web_contents(),
+    Metrics::RecordLiteScriptFinished(ukm_recorder_, ukm_source_id_,
                                       trigger_ui_type, state);
   }
   std::move(callback_).Run(state, std::move(trigger_context_), trigger_script);
diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h
index fb3e878..69ca094 100644
--- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h
+++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h
@@ -66,7 +66,8 @@
       const GURL& get_trigger_scripts_server,
       std::unique_ptr<StaticTriggerConditions> static_trigger_conditions,
       std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions,
-      ukm::UkmRecorder* ukm_recorder);
+      ukm::UkmRecorder* ukm_recorder,
+      ukm::SourceId ukm_source_id);
   ~TriggerScriptCoordinator() override;
   TriggerScriptCoordinator(const TriggerScriptCoordinator&) = delete;
   TriggerScriptCoordinator& operator=(const TriggerScriptCoordinator&) = delete;
@@ -113,8 +114,8 @@
   // tab-switcher.
   void OnTabInteractabilityChanged(bool interactable);
 
-  // Const access to the trigger context associated with this coordinator.
-  const TriggerContext& GetTriggerContext() const;
+  // Access to the trigger context associated with this coordinator.
+  TriggerContext& GetTriggerContext() const;
 
   // Returns the deeplink that this coordinator was started on.
   const GURL& GetDeeplink() const;
@@ -228,8 +229,9 @@
   // |remaining_trigger_condition_evaluations_|.
   int64_t initial_trigger_condition_evaluations_ = -1;
 
-  // The UKM recorder used for metrics.
+  // The UKM recorder and source id to use for metrics.
   ukm::UkmRecorder* const ukm_recorder_;
+  const ukm::SourceId ukm_source_id_;
 
   // Flag to ensure that we only get one LiteScriptFinished event per run.
   bool finished_state_recorded_ = false;
diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
index c811e5a..acb9aca 100644
--- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
+++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
@@ -90,13 +90,13 @@
       coordinator_->OnTriggerScriptShown(true);
     });
 
+    SimulateNavigateToUrl(GURL(kFakeDeepLink));
     coordinator_ = std::make_unique<TriggerScriptCoordinator>(
         &fake_platform_delegate_, web_contents(),
         std::move(mock_web_controller), std::move(mock_request_sender),
         GURL(kFakeServerUrl), std::move(mock_static_trigger_conditions),
-        std::move(mock_dynamic_trigger_conditions), &ukm_recorder_);
-
-    SimulateNavigateToUrl(GURL(kFakeDeepLink));
+        std::move(mock_dynamic_trigger_conditions), &ukm_recorder_,
+        ukm::GetSourceIdForWebContentsDocument(web_contents()));
   }
 
   void TearDown() override {
@@ -124,8 +124,7 @@
     auto entries =
         ukm_recorder_.GetEntriesByName("AutofillAssistant.LiteScriptFinished");
     ASSERT_THAT(entries.size(), Eq(1u));
-    ukm_recorder_.ExpectEntrySourceHasUrl(
-        entries[0], web_contents()->GetLastCommittedURL());
+    ukm_recorder_.ExpectEntrySourceHasUrl(entries[0], GURL(kFakeDeepLink));
     EXPECT_EQ(*ukm_recorder_.GetEntryMetric(entries[0], "TriggerUIType"),
               static_cast<int64_t>(type));
     EXPECT_EQ(*ukm_recorder_.GetEntryMetric(entries[0], "LiteScriptFinished"),
@@ -139,8 +138,7 @@
                                       int expected_times) {
     auto entries = ukm_recorder_.GetEntriesByName(
         "AutofillAssistant.LiteScriptShownToUser");
-    ukm_recorder_.ExpectEntrySourceHasUrl(
-        entries[0], web_contents()->GetLastCommittedURL());
+    ukm_recorder_.ExpectEntrySourceHasUrl(entries[0], GURL(kFakeDeepLink));
     int actual_times = 0;
     for (const auto* entry : entries) {
       if (*ukm_recorder_.GetEntryMetric(entry, "LiteScriptShownToUser") ==
@@ -159,8 +157,7 @@
       int expected_times) {
     auto entries = ukm_recorder_.GetEntriesByName(
         "AutofillAssistant.LiteScriptOnboarding");
-    ukm_recorder_.ExpectEntrySourceHasUrl(
-        entries[0], web_contents()->GetLastCommittedURL());
+    ukm_recorder_.ExpectEntrySourceHasUrl(entries[0], GURL(kFakeDeepLink));
     int actual_times = 0;
     for (const auto* entry : entries) {
       if (*ukm_recorder_.GetEntryMetric(entry, "LiteScriptOnboarding") ==
@@ -788,20 +785,20 @@
                       mock_callback_.Get());
 
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
-              SetURL(GURL("http://example.com/trigger_page")))
+              SetURL(GURL("https://example.com/trigger_page")))
       .Times(1);
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               GetPathPatternMatches(".*trigger_page.*"))
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_ui_delegate_, ShowTriggerScript).Times(1);
-  SimulateNavigateToUrl(GURL("http://example.com/trigger_page"));
+  SimulateNavigateToUrl(GURL("https://example.com/trigger_page"));
 }
 
 TEST_F(TriggerScriptCoordinatorTest, UrlChangeOutOfScheduleCheckDomainMatch) {
   GetTriggerScriptsResponseProto response;
   response.add_trigger_scripts()
       ->mutable_trigger_condition()
-      ->set_domain_with_scheme("http://example.com");
+      ->set_domain_with_scheme("https://example.com");
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
@@ -817,13 +814,13 @@
                       mock_callback_.Get());
 
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
-              SetURL(GURL("http://example.com/trigger_page")))
+              SetURL(GURL("https://example.com/trigger_page")))
       .Times(1);
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
-              GetDomainAndSchemeMatches(GURL("http://example.com")))
+              GetDomainAndSchemeMatches(GURL("https://example.com")))
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_ui_delegate_, ShowTriggerScript).Times(1);
-  SimulateNavigateToUrl(GURL("http://example.com/trigger_page"));
+  SimulateNavigateToUrl(GURL("https://example.com/trigger_page"));
 }
 
 TEST_F(TriggerScriptCoordinatorTest,
diff --git a/components/breadcrumbs/core/BUILD.gn b/components/breadcrumbs/core/BUILD.gn
index 196014a..e2a643a 100644
--- a/components/breadcrumbs/core/BUILD.gn
+++ b/components/breadcrumbs/core/BUILD.gn
@@ -22,6 +22,14 @@
   ]
 }
 
+source_set("feature_flags") {
+  sources = [
+    "features.cc",
+    "features.h",
+  ]
+  deps = [ "//base" ]
+}
+
 source_set("unit_tests") {
   testonly = true
   deps = [
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/features.mm b/components/breadcrumbs/core/features.cc
similarity index 62%
rename from ios/chrome/browser/crash_report/breadcrumbs/features.mm
rename to components/breadcrumbs/core/features.cc
index c588120e..f4775e0 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/features.mm
+++ b/components/breadcrumbs/core/features.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
+#include "components/breadcrumbs/core/features.h"
 
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
+namespace breadcrumbs {
 
 const base::Feature kLogBreadcrumbs{"LogBreadcrumbs",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace breadcrumbs
diff --git a/components/breadcrumbs/core/features.h b/components/breadcrumbs/core/features.h
new file mode 100644
index 0000000..e14a57e0
--- /dev/null
+++ b/components/breadcrumbs/core/features.h
@@ -0,0 +1,17 @@
+// 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 COMPONENTS_BREADCRUMBS_CORE_FEATURES_H_
+#define COMPONENTS_BREADCRUMBS_CORE_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace breadcrumbs {
+
+// Feature flag to log breadcrumb events.
+extern const base::Feature kLogBreadcrumbs;
+
+}  // namespace breadcrumbs
+
+#endif  // COMPONENTS_BREADCRUMBS_CORE_FEATURES_H_
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
index a2226f9..fccb8a6 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
@@ -33,6 +33,7 @@
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
 import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
+import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
 import org.chromium.media_session.mojom.MediaSessionAction;
 import org.chromium.services.media_session.MediaMetadata;
 
@@ -71,6 +72,16 @@
     public static final String ACTION_SEEK_BACKWARD =
             "MediaNotificationmanager.ListenerService.SEEK_BACKWARD";
 
+    // TODO(xingliu): These must match NotificationUmaTracker's action id. Remove these when
+    // notification code is modularized.
+    public static final int MEDIA_ACTION_PLAY = 17;
+    public static final int MEDIA_ACTION_PAUSE = 18;
+    public static final int MEDIA_ACTION_STOP = 19;
+    public static final int MEDIA_ACTION_PREVIOUS_TRACK = 20;
+    public static final int MEDIA_ACTION_NEXT_TRACK = 21;
+    public static final int MEDIA_ACTION_SEEK_FORWARD = 22;
+    public static final int MEDIA_ACTION_SEEK_BACKWARD = 23;
+
     // Overrides N detection. The production code will use |null|, which uses the Android version
     // code. Otherwise, |isRunningAtLeastN()| will return whatever value is set.
     @VisibleForTesting
@@ -251,9 +262,9 @@
         return true;
     }
 
-    private PendingIntent createPendingIntent(String action) {
+    private PendingIntentProvider createPendingIntent(String action) {
         Intent intent = mDelegate.createServiceIntent().setAction(action);
-        return PendingIntent.getService(getContext(), 0, intent,
+        return PendingIntentProvider.getService(getContext(), 0, intent,
                 PendingIntent.FLAG_CANCEL_CURRENT
                         | IntentUtils.getPendingIntentMutabilityFlag(false));
     }
@@ -278,10 +289,15 @@
         /** The intent string to be fired when this media button is clicked. */
         public String intentString;
 
-        public MediaButtonInfo(int buttonResId, int descriptionResId, String intentString) {
+        /** The ID to identify the notification button. */
+        public int buttonId;
+
+        public MediaButtonInfo(
+                int buttonResId, int descriptionResId, String intentString, int buttonId) {
             this.iconResId = buttonResId;
             this.descriptionResId = descriptionResId;
             this.intentString = intentString;
+            this.buttonId = buttonId;
         }
     }
 
@@ -313,25 +329,29 @@
 
         mActionToButtonInfo.put(MediaSessionAction.PLAY,
                 new MediaButtonInfo(R.drawable.ic_play_arrow_white_36dp,
-                        R.string.accessibility_play, ACTION_PLAY));
+                        R.string.accessibility_play, ACTION_PLAY, MEDIA_ACTION_PLAY));
         mActionToButtonInfo.put(MediaSessionAction.PAUSE,
                 new MediaButtonInfo(R.drawable.ic_pause_white_36dp, R.string.accessibility_pause,
-                        ACTION_PAUSE));
+                        ACTION_PAUSE, MEDIA_ACTION_PAUSE));
         mActionToButtonInfo.put(MediaSessionAction.STOP,
-                new MediaButtonInfo(
-                        R.drawable.ic_stop_white_36dp, R.string.accessibility_stop, ACTION_STOP));
+                new MediaButtonInfo(R.drawable.ic_stop_white_36dp, R.string.accessibility_stop,
+                        ACTION_STOP, MEDIA_ACTION_STOP));
         mActionToButtonInfo.put(MediaSessionAction.PREVIOUS_TRACK,
                 new MediaButtonInfo(R.drawable.ic_skip_previous_white_36dp,
-                        R.string.accessibility_previous_track, ACTION_PREVIOUS_TRACK));
+                        R.string.accessibility_previous_track, ACTION_PREVIOUS_TRACK,
+                        MEDIA_ACTION_PREVIOUS_TRACK));
         mActionToButtonInfo.put(MediaSessionAction.NEXT_TRACK,
                 new MediaButtonInfo(R.drawable.ic_skip_next_white_36dp,
-                        R.string.accessibility_next_track, ACTION_NEXT_TRACK));
+                        R.string.accessibility_next_track, ACTION_NEXT_TRACK,
+                        MEDIA_ACTION_NEXT_TRACK));
         mActionToButtonInfo.put(MediaSessionAction.SEEK_FORWARD,
                 new MediaButtonInfo(R.drawable.ic_fast_forward_white_36dp,
-                        R.string.accessibility_seek_forward, ACTION_SEEK_FORWARD));
+                        R.string.accessibility_seek_forward, ACTION_SEEK_FORWARD,
+                        MEDIA_ACTION_SEEK_FORWARD));
         mActionToButtonInfo.put(MediaSessionAction.SEEK_BACKWARD,
                 new MediaButtonInfo(R.drawable.ic_fast_rewind_white_36dp,
-                        R.string.accessibility_seek_backward, ACTION_SEEK_BACKWARD));
+                        R.string.accessibility_seek_backward, ACTION_SEEK_BACKWARD,
+                        MEDIA_ACTION_SEEK_BACKWARD));
 
         mThrottler = new Throttler(this);
     }
@@ -612,7 +632,7 @@
         // The intent will currently only be null when using a custom tab.
         // TODO(avayvod) work out what we should do in this case. See https://crbug.com/585395.
         if (mMediaNotificationInfo.contentIntent != null) {
-            mNotificationBuilder.setContentIntent(PendingIntent.getActivity(getContext(),
+            mNotificationBuilder.setContentIntent(PendingIntentProvider.getActivity(getContext(),
                     mMediaNotificationInfo.instanceId, mMediaNotificationInfo.contentIntent,
                     PendingIntent.FLAG_UPDATE_CURRENT));
             // Set FLAG_UPDATE_CURRENT so that the intent extras is updated, otherwise the
@@ -760,7 +780,7 @@
             MediaButtonInfo buttonInfo = mActionToButtonInfo.get(action);
             builder.addAction(buttonInfo.iconResId,
                     getContext().getResources().getString(buttonInfo.descriptionResId),
-                    createPendingIntent(buttonInfo.intentString));
+                    createPendingIntent(buttonInfo.intentString), buttonInfo.buttonId);
         }
 
         // Only apply MediaStyle when NotificationInfo supports play/pause.
diff --git a/components/data_use_measurement/core/data_use_tracker_prefs.cc b/components/data_use_measurement/core/data_use_tracker_prefs.cc
index e29bedb0..f688d77 100644
--- a/components/data_use_measurement/core/data_use_tracker_prefs.cc
+++ b/components/data_use_measurement/core/data_use_tracker_prefs.cc
@@ -17,6 +17,8 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/time/clock.h"
+#include "base/time/time.h"
+#include "base/value_iterators.h"
 #include "build/build_config.h"
 #include "components/data_use_measurement/core/data_use_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -78,7 +80,8 @@
     base::Time key_date;
     if (base::Time::FromUTCString(it.first.c_str(), &key_date) &&
         key_date > last_date) {
-      user_pref_new_dict.Set(it.first, it.second.CreateDeepCopy());
+      user_pref_new_dict.Set(it.first,
+                             base::Value::ToUniquePtrValue(it.second.Clone()));
     }
   }
   pref_service_->Set(pref_name, user_pref_new_dict);
diff --git a/components/favicon/content/content_favicon_driver.cc b/components/favicon/content/content_favicon_driver.cc
index 673363869..9e69e8f 100644
--- a/components/favicon/content/content_favicon_driver.cc
+++ b/components/favicon/content/content_favicon_driver.cc
@@ -47,14 +47,28 @@
   return entry ? entry->GetURL() : GURL();
 }
 
+GURL ContentFaviconDriver::GetManifestURL(content::RenderFrameHost* rfh) {
+  DocumentManifestData* document_data =
+      DocumentManifestData::GetOrCreateForCurrentDocument(rfh);
+  return document_data->has_manifest_url ? rfh->ManifestURL() : GURL();
+}
+
 ContentFaviconDriver::ContentFaviconDriver(content::WebContents* web_contents,
                                            CoreFaviconService* favicon_service)
     : content::WebContentsObserver(web_contents),
-      FaviconDriverImpl(favicon_service),
-      document_on_load_completed_(false) {}
+      FaviconDriverImpl(favicon_service) {}
 
 ContentFaviconDriver::~ContentFaviconDriver() = default;
 
+ContentFaviconDriver::DocumentManifestData::DocumentManifestData(
+    content::RenderFrameHost* render_frame_host) {}
+ContentFaviconDriver::DocumentManifestData::~DocumentManifestData() = default;
+
+ContentFaviconDriver::NavigationManifestData::NavigationManifestData(
+    content::NavigationHandle& navigation_handle) {}
+ContentFaviconDriver::NavigationManifestData::~NavigationManifestData() =
+    default;
+
 void ContentFaviconDriver::OnDidDownloadManifest(
     ManifestDownloadCallback callback,
     const GURL& manifest_url,
@@ -143,23 +157,16 @@
   // occur when loading an initially blank page.
   content::NavigationEntry* entry =
       web_contents()->GetController().GetLastCommittedEntry();
+
   if (!entry)
     return;
 
-  // We update |favicon_urls_| even if the list is believed to be partial
-  // (checked below), because callers of our getter favicon_urls() expect so.
-  std::vector<blink::mojom::FaviconURL> favicon_urls;
-  for (const auto& candidate : candidates)
-    favicon_urls.push_back(*candidate);
-  favicon_urls_ = favicon_urls;
-
-  if (!document_on_load_completed_)
+  if (!rfh->IsDocumentOnLoadCompletedInMainFrame())
     return;
 
-  OnUpdateCandidates(entry->GetURL(),
-                     FaviconURLsFromContentFaviconURLs(favicon_urls_.value_or(
-                         std::vector<blink::mojom::FaviconURL>())),
-                     manifest_url_);
+  OnUpdateCandidates(rfh->GetLastCommittedURL(),
+                     FaviconURLsFromContentFaviconURLs(candidates),
+                     GetManifestURL(rfh));
 }
 
 void ContentFaviconDriver::DidUpdateWebManifestURL(
@@ -169,37 +176,39 @@
   // occur when loading an initially blank page.
   content::NavigationEntry* entry =
       web_contents()->GetController().GetLastCommittedEntry();
-  if (!entry || !document_on_load_completed_)
+  if (!entry || !rfh->IsDocumentOnLoadCompletedInMainFrame())
     return;
 
-  manifest_url_ = manifest_url.value_or(GURL());
+  DocumentManifestData* document_data =
+      DocumentManifestData::GetOrCreateForCurrentDocument(rfh);
+  document_data->has_manifest_url = true;
 
   // On regular page loads, DidUpdateManifestURL() is guaranteed to be called
   // before DidUpdateFaviconURL(). However, a page can update the favicons via
   // javascript.
-  if (favicon_urls_.has_value()) {
-    OnUpdateCandidates(entry->GetURL(),
-                       FaviconURLsFromContentFaviconURLs(*favicon_urls_),
-                       manifest_url_);
+  if (!rfh->FaviconURLs().empty()) {
+    OnUpdateCandidates(rfh->GetLastCommittedURL(),
+                       FaviconURLsFromContentFaviconURLs(rfh->FaviconURLs()),
+                       GetManifestURL(rfh));
   }
 }
 
 void ContentFaviconDriver::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!navigation_handle->IsInMainFrame())
+  if (!navigation_handle->IsInPrimaryMainFrame())
     return;
 
-  favicon_urls_.reset();
-
-  if (!navigation_handle->IsSameDocument()) {
-    document_on_load_completed_ = false;
-    manifest_url_ = GURL();
-  }
-
   content::ReloadType reload_type = navigation_handle->GetReloadType();
   if (reload_type == content::ReloadType::NONE || IsOffTheRecord())
     return;
 
+  if (!navigation_handle->IsSameDocument()) {
+    NavigationManifestData* navigation_data =
+        NavigationManifestData::GetOrCreateForNavigationHandle(
+            *navigation_handle);
+    navigation_data->has_manifest_url = false;
+  }
+
   bypass_cache_page_url_ = navigation_handle->GetURL();
   SetFaviconOutOfDateForPage(
       navigation_handle->GetURL(),
@@ -208,12 +217,20 @@
 
 void ContentFaviconDriver::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!navigation_handle->IsInMainFrame() ||
-      !navigation_handle->HasCommitted() ||
-      navigation_handle->IsErrorPage()) {
+  if (!navigation_handle->IsInPrimaryMainFrame() ||
+      !navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) {
     return;
   }
 
+  // Transfer in-flight navigation data to the document user data.
+  NavigationManifestData* navigation_data =
+      NavigationManifestData::GetOrCreateForNavigationHandle(
+          *navigation_handle);
+  DocumentManifestData* document_data =
+      DocumentManifestData::GetOrCreateForCurrentDocument(
+          navigation_handle->GetRenderFrameHost());
+  document_data->has_manifest_url = navigation_data->has_manifest_url;
+
   // Wait till the user navigates to a new URL to start checking the cache
   // again. The cache may be ignored for non-reload navigations (e.g.
   // history.replace() in-page navigation). This is allowed to increase the
@@ -229,11 +246,10 @@
   FetchFavicon(url, navigation_handle->IsSameDocument());
 }
 
-void ContentFaviconDriver::DocumentOnLoadCompletedInMainFrame(
-    content::RenderFrameHost* render_frame_host) {
-  document_on_load_completed_ = true;
-}
-
+NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(
+    ContentFaviconDriver::NavigationManifestData)
+RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(
+    ContentFaviconDriver::DocumentManifestData)
 WEB_CONTENTS_USER_DATA_KEY_IMPL(ContentFaviconDriver)
 
 }  // namespace favicon
diff --git a/components/favicon/content/content_favicon_driver.h b/components/favicon/content/content_favicon_driver.h
index 68903ee..16ec8bbe 100644
--- a/components/favicon/content/content_favicon_driver.h
+++ b/components/favicon/content/content_favicon_driver.h
@@ -10,7 +10,9 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "components/favicon/core/favicon_driver_impl.h"
+#include "content/public/browser/navigation_handle_user_data.h"
 #include "content/public/browser/reload_type.h"
+#include "content/public/browser/render_document_host_user_data.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
@@ -30,17 +32,13 @@
  public:
   ~ContentFaviconDriver() override;
 
-  // Returns the current tab's favicon URLs. If this is empty,
-  // DidUpdateFaviconURL has not yet been called for the current navigation.
-  std::vector<blink::mojom::FaviconURL> favicon_urls() const {
-    return favicon_urls_.value_or(std::vector<blink::mojom::FaviconURL>());
-  }
-
   // FaviconDriver implementation.
   gfx::Image GetFavicon() const override;
   bool FaviconIsValid() const override;
   GURL GetActiveURL() override;
 
+  GURL GetManifestURL(content::RenderFrameHost* rfh);
+
  protected:
   ContentFaviconDriver(content::WebContents* web_contents,
                        CoreFaviconService* favicon_service);
@@ -48,6 +46,25 @@
  private:
   friend class content::WebContentsUserData<ContentFaviconDriver>;
 
+  // TODO(crbug.com/1205018): these two classes are current used to ensure that
+  // we disregard manifest URL updates that arrive prior to onload firing.
+  struct DocumentManifestData
+      : public content::RenderDocumentHostUserData<DocumentManifestData> {
+    explicit DocumentManifestData(content::RenderFrameHost* rfh);
+    ~DocumentManifestData() override;
+    RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL();
+    bool has_manifest_url = false;
+  };
+
+  struct NavigationManifestData
+      : public content::NavigationHandleUserData<NavigationManifestData> {
+    explicit NavigationManifestData(
+        content::NavigationHandle& navigation_handle);
+    ~NavigationManifestData() override;
+    NAVIGATION_HANDLE_USER_DATA_KEY_DECL();
+    bool has_manifest_url = false;
+  };
+
   // Callback when a manifest is downloaded.
   void OnDidDownloadManifest(ManifestDownloadCallback callback,
                              const GURL& manifest_url,
@@ -80,15 +97,8 @@
       content::NavigationHandle* navigation_handle) override;
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
-  void DocumentOnLoadCompletedInMainFrame(
-      content::RenderFrameHost* render_frame_host) override;
 
-  bool document_on_load_completed_;
   GURL bypass_cache_page_url_;
-  // nullopt until the actual list is reported via DidUpdateFaviconURL().
-  base::Optional<std::vector<blink::mojom::FaviconURL>> favicon_urls_;
-  // Web Manifest URL or empty URL if none.
-  GURL manifest_url_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
diff --git a/components/favicon/content/content_favicon_driver_unittest.cc b/components/favicon/content/content_favicon_driver_unittest.cc
index a7ae1b5..084f4bf1 100644
--- a/components/favicon/content/content_favicon_driver_unittest.cc
+++ b/components/favicon/content/content_favicon_driver_unittest.cc
@@ -28,9 +28,9 @@
 namespace favicon {
 namespace {
 
+using testing::_;
 using testing::Return;
 using testing::SizeIs;
-using testing::_;
 
 void TestFetchFaviconForPage(
     content::WebContents* web_contents,
@@ -50,6 +50,7 @@
   const std::vector<SkBitmap> kEmptyIcons;
   const GURL kPageURL = GURL("http://www.google.com/");
   const GURL kIconURL = GURL("http://www.google.com/favicon.ico");
+  const GURL kFakeManifestURL = GURL("http://www.google.com/manifest.json");
 
   ContentFaviconDriverTest() {
     ON_CALL(favicon_service_, UpdateFaviconMappingsAndFetch(_, _, _, _, _, _))
@@ -103,6 +104,48 @@
       kIconURL, 200, kEmptyIcons, kEmptyIconSizes));
 }
 
+// Ensures that we do not consider a manifest URL if it arrives before the
+// onload handler has fired.
+// TODO(crbug.com/1205018): This may not necessarily the desired behavior, but
+// this test will prevent unintentional behavioral changes until the issue is
+// resolved.
+TEST_F(ContentFaviconDriverTest, IgnoreManifestURLBeforeOnLoad) {
+  ContentFaviconDriver* favicon_driver =
+      ContentFaviconDriver::FromWebContents(web_contents());
+  auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
+      kPageURL, web_contents());
+  navigation->SetKeepLoading(true);
+  navigation->Commit();
+  base::Optional<GURL> manifest_url = kFakeManifestURL;
+  auto* rfh_tester =
+      content::RenderFrameHostTester::For(web_contents()->GetMainFrame());
+  rfh_tester->SimulateManifestURLUpdate(manifest_url);
+  static_cast<content::WebContentsObserver*>(favicon_driver)
+      ->DidUpdateWebManifestURL(web_contents()->GetMainFrame(), manifest_url);
+  EXPECT_EQ(GURL(),
+            favicon_driver->GetManifestURL(web_contents()->GetMainFrame()));
+}
+
+// Ensures that we use a manifest URL if it arrives after the onload handler
+// has fired. See crbug.com/1205018 for details.
+TEST_F(ContentFaviconDriverTest, UseManifestURLAFterOnLoad) {
+  ContentFaviconDriver* favicon_driver =
+      ContentFaviconDriver::FromWebContents(web_contents());
+  auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
+      kPageURL, web_contents());
+  navigation->SetKeepLoading(true);
+  navigation->Commit();
+  navigation->StopLoading();
+  base::Optional<GURL> manifest_url = kFakeManifestURL;
+  auto* rfh_tester =
+      content::RenderFrameHostTester::For(web_contents()->GetMainFrame());
+  rfh_tester->SimulateManifestURLUpdate(manifest_url);
+  static_cast<content::WebContentsObserver*>(favicon_driver)
+      ->DidUpdateWebManifestURL(web_contents()->GetMainFrame(), manifest_url);
+  EXPECT_EQ(kFakeManifestURL,
+            favicon_driver->GetManifestURL(web_contents()->GetMainFrame()));
+}
+
 // Test that no download is initiated when DocumentOnLoadCompletedInMainFrame()
 // is not triggered (e.g. user stopped an ongoing page load).
 TEST_F(ContentFaviconDriverTest, ShouldNotCauseImageDownload) {
@@ -120,9 +163,6 @@
   base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(web_contents_tester()->HasPendingDownloadImage(kIconURL));
-
-  // Nevertheless, we expect the list exposed via favicon_urls().
-  EXPECT_THAT(favicon_driver->favicon_urls(), SizeIs(1));
 }
 
 // Test that Favicon is not requested repeatedly during the same session if
@@ -155,25 +195,6 @@
   EXPECT_TRUE(web_contents_tester()->HasPendingDownloadImage(kOtherIconURL));
 }
 
-// Test that ContentFaviconDriver ignores updated favicon URLs if there is no
-// last committed entry. This occurs when script is injected in about:blank.
-// See crbug.com/520759 for more details
-TEST_F(ContentFaviconDriverTest, FaviconUpdateNoLastCommittedEntry) {
-  ASSERT_EQ(nullptr, web_contents()->GetController().GetLastCommittedEntry());
-
-  std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(
-      GURL("http://www.google.ca/favicon.ico"),
-      blink::mojom::FaviconIconType::kFavicon, kEmptyIconSizes));
-  favicon::ContentFaviconDriver* driver =
-      favicon::ContentFaviconDriver::FromWebContents(web_contents());
-  static_cast<content::WebContentsObserver*>(driver)->DidUpdateFaviconURL(
-      web_contents()->GetMainFrame(), favicon_urls);
-
-  // Test that ContentFaviconDriver ignored the favicon url update.
-  EXPECT_TRUE(driver->favicon_urls().empty());
-}
-
 using ContentFaviconDriverTestNoFaviconService =
     content::RenderViewHostTestHarness;
 
diff --git a/components/favicon/content/favicon_url_util.cc b/components/favicon/content/favicon_url_util.cc
index edd71f1..b27692c 100644
--- a/components/favicon/content/favicon_url_util.cc
+++ b/components/favicon/content/favicon_url_util.cc
@@ -32,14 +32,14 @@
 }  // namespace
 
 FaviconURL FaviconURLFromContentFaviconURL(
-    const blink::mojom::FaviconURL& favicon_url) {
-  return FaviconURL(favicon_url.icon_url,
-                    IconTypeFromContentIconType(favicon_url.icon_type),
-                    favicon_url.icon_sizes);
+    const blink::mojom::FaviconURLPtr& favicon_url) {
+  return FaviconURL(favicon_url->icon_url,
+                    IconTypeFromContentIconType(favicon_url->icon_type),
+                    favicon_url->icon_sizes);
 }
 
 std::vector<FaviconURL> FaviconURLsFromContentFaviconURLs(
-    const std::vector<blink::mojom::FaviconURL>& favicon_urls) {
+    const std::vector<blink::mojom::FaviconURLPtr>& favicon_urls) {
   std::vector<FaviconURL> result;
   result.reserve(favicon_urls.size());
   std::transform(favicon_urls.begin(), favicon_urls.end(),
diff --git a/components/favicon/content/favicon_url_util.h b/components/favicon/content/favicon_url_util.h
index af3abc8..fbbef8ff 100644
--- a/components/favicon/content/favicon_url_util.h
+++ b/components/favicon/content/favicon_url_util.h
@@ -14,11 +14,11 @@
 
 // Creates a favicon::FaviconURL from a blink::mojom::FaviconURL.
 FaviconURL FaviconURLFromContentFaviconURL(
-    const blink::mojom::FaviconURL& favicon_url);
+    const blink::mojom::FaviconURLPtr& favicon_url);
 
-// Creates favicon::FaviconURLs from content::FaviconURLs.
+// Creates favicon::FaviconURLs from blink::mojom::FaviconURLPtrs.
 std::vector<FaviconURL> FaviconURLsFromContentFaviconURLs(
-    const std::vector<blink::mojom::FaviconURL>& favicon_urls);
+    const std::vector<blink::mojom::FaviconURLPtr>& favicon_urls);
 
 }  // namespace favicon
 
diff --git a/components/favicon/core/favicon_handler.cc b/components/favicon/core/favicon_handler.cc
index 0d3919e..9006a26f 100644
--- a/components/favicon/core/favicon_handler.cc
+++ b/components/favicon/core/favicon_handler.cc
@@ -86,7 +86,7 @@
 bool HasValidResult(
     const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) {
   return std::find_if(bitmap_results.begin(), bitmap_results.end(), IsValid) !=
-      bitmap_results.end();
+         bitmap_results.end();
 }
 
 std::vector<int> GetDesiredPixelSizes(
@@ -171,8 +171,7 @@
   DCHECK(delegate_);
 }
 
-FaviconHandler::~FaviconHandler() {
-}
+FaviconHandler::~FaviconHandler() = default;
 
 // static
 favicon_base::IconTypeSet FaviconHandler::GetIconTypesFromHandlerType(
@@ -297,8 +296,7 @@
   DCHECK(!favicon_bitmap_results.empty());
 
   gfx::Image resized_image = favicon_base::SelectFaviconFramesFromPNGs(
-      favicon_bitmap_results,
-      favicon_base::GetFaviconScales(),
+      favicon_bitmap_results, favicon_base::GetFaviconScales(),
       preferred_icon_size());
   // The history service sends back results for a single icon URL and icon
   // type, so it does not matter which result we get |icon_url| and |icon_type|
@@ -532,10 +530,8 @@
       image_skia =
           gfx::ImageSkia::CreateFrom1xBitmap(bitmaps[best_indices.front()]);
     } else {
-      image_skia = CreateFaviconImageSkia(bitmaps,
-                                          original_bitmap_sizes,
-                                          preferred_icon_size(),
-                                          &score);
+      image_skia = CreateFaviconImageSkia(bitmaps, original_bitmap_sizes,
+                                          preferred_icon_size(), &score);
     }
 
     if (!image_skia.isNull() && score > best_favicon_.candidate.score) {
@@ -675,8 +671,9 @@
   }
 }
 
-void FaviconHandler::OnFaviconData(const std::vector<
-    favicon_base::FaviconRawBitmapResult>& favicon_bitmap_results) {
+void FaviconHandler::OnFaviconData(
+    const std::vector<favicon_base::FaviconRawBitmapResult>&
+        favicon_bitmap_results) {
   bool has_valid_result = HasValidResult(favicon_bitmap_results);
   // For off-the-record profiles pretend that favicons from FaviconService are
   // expired so websites don't know if a site was previously visited in regular
diff --git a/components/favicon/core/favicon_handler.h b/components/favicon/core/favicon_handler.h
index 574d88a..80b34ec 100644
--- a/components/favicon/core/favicon_handler.h
+++ b/components/favicon/core/favicon_handler.h
@@ -263,7 +263,6 @@
   // - A mapping is known to exist (reflected by |notification_icon_type_|).
   // - All download attempts returned 404s OR no relevant candidate was
   //   provided (as per |icon_types_|).
-  // - The corresponding feature is enabled (currently behind variations).
   void MaybeDeleteFaviconMappings();
 
   // Notifies |driver_| that FaviconHandler found an icon which matches the
diff --git a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
index 5c12763..c5830cc2 100644
--- a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.cc
@@ -8,12 +8,11 @@
 #include "base/rand_util.h"
 #include "content/public/browser/render_frame_host.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
+#include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom.h"
 
+using FeatureType = blink::mojom::UseCounterFeatureType;
 using UkmFeatureList = UseCounterPageLoadMetricsObserver::UkmFeatureList;
 using WebFeature = blink::mojom::WebFeature;
-using WebFeatureBitSet =
-    std::bitset<static_cast<size_t>(WebFeature::kNumberOfFeatures)>;
-
 using CSSSampleId = blink::mojom::CSSSampleId;
 
 namespace {
@@ -47,21 +46,12 @@
   }
 }
 
-void RecordMainFrameFeature(blink::mojom::WebFeature feature) {
-  UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramMainFrameName, feature);
-}
-
-void RecordFeature(blink::mojom::WebFeature feature) {
-  UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramName, feature);
-}
-
-void RecordCssProperty(CSSSampleId property) {
-  UMA_HISTOGRAM_ENUMERATION(internal::kCssPropertiesHistogramName, property);
-}
-
-void RecordAnimatedCssProperty(CSSSampleId animated_property) {
-  UMA_HISTOGRAM_ENUMERATION(internal::kAnimatedCssPropertiesHistogramName,
-                            animated_property);
+template <size_t N>
+bool TestAndSet(std::bitset<N>& bitset,
+                blink::UseCounterFeature::EnumValue value) {
+  bool has_record = bitset.test(value);
+  bitset.set(value);
+  return has_record;
 }
 
 }  // namespace
@@ -77,82 +67,46 @@
     content::NavigationHandle* navigation_handle,
     ukm::SourceId source_id) {
   // Verify that no feature usage is observed before commit
-  DCHECK_LE(features_recorded_.count(), 0ul);
-  DCHECK_LE(main_frame_features_recorded_.count(), 0ul);
+  DCHECK_EQ(features_recorded_.count(), 0ul);
+  DCHECK_EQ(main_frame_features_recorded_.count(), 0ul);
+  DCHECK_EQ(ukm_features_recorded_.count(), 0ul);
+  DCHECK_EQ(css_properties_recorded_.count(), 0ul);
+  DCHECK_EQ(animated_css_properties_recorded_.count(), 0ul);
+
+  content::RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost();
+
+  auto web_feature_page_visit =
+      static_cast<blink::UseCounterFeature::EnumValue>(WebFeature::kPageVisits);
 
   ukm::builders::Blink_UseCounter(source_id)
-      .SetFeature(static_cast<size_t>(WebFeature::kPageVisits))
+      .SetFeature(web_feature_page_visit)
       .SetIsMainFrameFeature(1)
       .Record(ukm::UkmRecorder::Get());
-  ukm_features_recorded_.set(static_cast<size_t>(WebFeature::kPageVisits));
-  RecordFeature(WebFeature::kPageVisits);
-  RecordMainFrameFeature(WebFeature::kPageVisits);
-  RecordCssProperty(CSSSampleId::kTotalPagesMeasured);
-  RecordAnimatedCssProperty(CSSSampleId::kTotalPagesMeasured);
-  features_recorded_.set(static_cast<size_t>(WebFeature::kPageVisits));
-  main_frame_features_recorded_.set(
-      static_cast<size_t>(WebFeature::kPageVisits));
+  ukm_features_recorded_.set(web_feature_page_visit);
+
+  RecordMainFrameWebFeature(rfh, WebFeature::kPageVisits);
+  RecordUseCounterFeature(rfh,
+                          {FeatureType::kWebFeature, web_feature_page_visit});
+
+  auto css_total_pages_measured =
+      static_cast<blink::UseCounterFeature::EnumValue>(
+          CSSSampleId::kTotalPagesMeasured);
+  RecordUseCounterFeature(
+      rfh, {FeatureType::kCssProperty, css_total_pages_measured});
+  RecordUseCounterFeature(
+      rfh, {FeatureType::kAnimatedCssProperty, css_total_pages_measured});
+
   return CONTINUE_OBSERVING;
 }
 
 void UseCounterPageLoadMetricsObserver::OnFeaturesUsageObserved(
     content::RenderFrameHost* rfh,
     const std::vector<blink::UseCounterFeature>& features) {
-  using FeatureType = blink::mojom::UseCounterFeatureType;
   for (const blink::UseCounterFeature& feature : features) {
-    switch (feature.type()) {
-      case FeatureType::kWebFeature: {
-        WebFeature web_feature = static_cast<WebFeature>(feature.value());
-        // Record feature usage in main frame.
-        // If a feature is already recorded in the main frame, it is also
-        // recorded on the page.
-        if (main_frame_features_recorded_.test(feature.value()))
-          continue;
-        if (rfh->GetParent() == nullptr) {
-          RecordMainFrameFeature(web_feature);
-          main_frame_features_recorded_.set(feature.value());
-        }
-
-        if (features_recorded_.test(feature.value()))
-          continue;
-        PossiblyWarnFeatureDeprecation(rfh, web_feature);
-        RecordFeature(web_feature);
-        features_recorded_.set(feature.value());
-        break;
-      }
-      case FeatureType::kCssProperty: {
-        CSSSampleId css_property = static_cast<CSSSampleId>(feature.value());
-        // Same as above, the usage of each CSS property should be only measured
-        // once.
-        if (css_properties_recorded_.test(feature.value()))
-          continue;
-        // There are about 600 enums, so the memory required for a vector
-        // histogram is about 600 * 8 byes = 5KB 50% of the time there are about
-        // 100 CSS properties recorded per page load. Storage in sparce
-        // histogram entries are 48 bytes instead of 8 bytes so the memory
-        // required for a sparse histogram is about 100 * 48 bytes = 5KB. On top
-        // there will be std::map overhead and the acquire/release of a
-        // base::Lock to protect the map during each update. Overal it is still
-        // better to use a vector histogram here since it is faster to access
-        // and merge and uses about same amount of memory.
-        RecordCssProperty(css_property);
-        css_properties_recorded_.set(feature.value());
-        break;
-      }
-      case FeatureType::kAnimatedCssProperty: {
-        CSSSampleId animated_css_property =
-            static_cast<CSSSampleId>(feature.value());
-        // Same as above, the usage of each animated CSS property should be only
-        // measured once.
-        if (animated_css_properties_recorded_.test(feature.value()))
-          continue;
-        // See comments above (in the css property section) for reasoning of
-        // using a vector histogram here instead of a sparse histogram.
-        RecordAnimatedCssProperty(animated_css_property);
-        animated_css_properties_recorded_.set(feature.value());
-        break;
-      }
+    if (feature.type() == FeatureType::kWebFeature) {
+      RecordMainFrameWebFeature(rfh, static_cast<WebFeature>(feature.value()));
     }
+    RecordUseCounterFeature(rfh, feature);
   }
 }
 
@@ -190,18 +144,77 @@
   return CONTINUE_OBSERVING;
 }
 
+void UseCounterPageLoadMetricsObserver::RecordUseCounterFeature(
+    content::RenderFrameHost* rfh,
+    const blink::UseCounterFeature& feature) {
+  // Note: UMA_HISTOGRAM_ENUMERATION does accept a 3rd parameter as value bound,
+  // when the second parameter is a general integer type, but it requires the
+  // 3rd parameter to be constexpr in order to pass an internal static check.
+  // Writing something like
+  // UMA_HISTOGRAM_ENUMERATION(histogram_name, feature.value(),
+  //                           feature.max_value());
+  // will cause compile error.
+  switch (feature.type()) {
+    case FeatureType::kWebFeature: {
+      auto web_feature = static_cast<WebFeature>(feature.value());
+      if (TestAndSet(features_recorded_, feature.value()))
+        return;
+      UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramName, web_feature);
+      PossiblyWarnFeatureDeprecation(rfh, web_feature);
+      break;
+    }
+    // There are about 600 enums, so the memory required for a vector
+    // histogram is about 600 * 8 byes = 5KB 50% of the time there are about
+    // 100 CSS properties recorded per page load. Storage in sparce
+    // histogram entries are 48 bytes instead of 8 bytes so the memory
+    // required for a sparse histogram is about 100 * 48 bytes = 5KB. On top
+    // there will be std::map overhead and the acquire/release of a
+    // base::Lock to protect the map during each update. Overal it is still
+    // better to use a vector histogram here since it is faster to access
+    // and merge and uses about same amount of memory.
+    case FeatureType::kCssProperty:
+      if (TestAndSet(css_properties_recorded_, feature.value()))
+        return;
+      UMA_HISTOGRAM_ENUMERATION(internal::kCssPropertiesHistogramName,
+                                static_cast<CSSSampleId>(feature.value()));
+      break;
+    case FeatureType::kAnimatedCssProperty:
+      if (TestAndSet(animated_css_properties_recorded_, feature.value()))
+        return;
+      UMA_HISTOGRAM_ENUMERATION(internal::kAnimatedCssPropertiesHistogramName,
+                                static_cast<CSSSampleId>(feature.value()));
+      break;
+  }
+}
+
+void UseCounterPageLoadMetricsObserver::RecordMainFrameWebFeature(
+    content::RenderFrameHost* rfh,
+    blink::mojom::WebFeature web_feature) {
+  if (rfh->GetParent() != nullptr)
+    return;
+
+  if (TestAndSet(main_frame_features_recorded_,
+                 static_cast<size_t>(web_feature))) {
+    return;
+  }
+  UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramMainFrameName,
+                            web_feature);
+}
+
 void UseCounterPageLoadMetricsObserver::RecordUkmFeatures() {
-  for (auto feature : GetAllowedUkmFeatures()) {
-    if (!features_recorded_.test(static_cast<size_t>(feature)))
+  for (WebFeature web_feature : GetAllowedUkmFeatures()) {
+    auto feature_enum_value =
+        static_cast<blink::UseCounterFeature::EnumValue>(web_feature);
+    if (!features_recorded_.test(feature_enum_value))
       continue;
-    if (ukm_features_recorded_.test(static_cast<size_t>(feature)))
+
+    if (TestAndSet(ukm_features_recorded_, feature_enum_value))
       continue;
-    ukm_features_recorded_.set(static_cast<size_t>(feature));
 
     ukm::builders::Blink_UseCounter(GetDelegate().GetPageUkmSourceId())
-        .SetFeature(static_cast<size_t>(feature))
+        .SetFeature(feature_enum_value)
         .SetIsMainFrameFeature(
-            main_frame_features_recorded_.test(static_cast<size_t>(feature)))
+            main_frame_features_recorded_.test(feature_enum_value))
         .Record(ukm::UkmRecorder::Get());
   }
 }
diff --git a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h
index 04c16f2a..54ed965 100644
--- a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
 #include "third_party/blink/public/mojom/use_counter/css_property_id.mojom.h"
+#include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom-forward.h"
 #include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
 
 namespace internal {
@@ -53,6 +54,18 @@
   // Returns a list of opt-in UKM features for use counter.
   static const UkmFeatureList& GetAllowedUkmFeatures();
 
+  // Records an `UseCounterFeature` through UMA_HISTOGRAM_ENUMERATION if
+  // the feature has not been recorded before.
+  void RecordUseCounterFeature(content::RenderFrameHost*,
+                               const blink::UseCounterFeature&);
+
+  // Records a WebFeature in main frame if `rfh` is a main frame and the feature
+  // has not been recorded before.
+  void RecordMainFrameWebFeature(content::RenderFrameHost*,
+                                 blink::mojom::WebFeature);
+
+  // Records UKM subset of WebFeatures, if the WebFeature is observed in the
+  // page.
   void RecordUkmFeatures();
 
   // To keep tracks of which features have been measured.
diff --git a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer_unittest.cc
index 15654ee9..63c42a3 100644
--- a/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer_unittest.cc
+++ b/components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer_unittest.cc
@@ -20,6 +20,19 @@
 const char kTestUrl[] = "https://www.google.com";
 using WebFeature = blink::mojom::WebFeature;
 using CSSSampleId = blink::mojom::CSSSampleId;
+using FeatureType = blink::mojom::UseCounterFeatureType;
+
+const char* GetUseCounterHistogramName(
+    blink::mojom::UseCounterFeatureType feature_type) {
+  switch (feature_type) {
+    case FeatureType::kWebFeature:
+      return internal::kFeaturesHistogramName;
+    case FeatureType::kCssProperty:
+      return internal::kCssPropertiesHistogramName;
+    case FeatureType::kAnimatedCssProperty:
+      return internal::kAnimatedCssPropertiesHistogramName;
+  }
+}
 
 }  // namespace
 
@@ -28,10 +41,24 @@
  public:
   UseCounterPageLoadMetricsObserverTest() {}
 
+  void ExpectBucketCount(const blink::UseCounterFeature& feature,
+                         size_t count) {
+    if (feature.type() == blink::mojom::UseCounterFeatureType::kWebFeature) {
+      tester()->histogram_tester().ExpectBucketCount(
+          internal::kFeaturesHistogramMainFrameName,
+          static_cast<base::Histogram::Sample>(feature.value()), count);
+    }
+
+    tester()->histogram_tester().ExpectBucketCount(
+        GetUseCounterHistogramName(feature.type()),
+        static_cast<base::Histogram::Sample>(feature.value()), count);
+  }
+
   void HistogramBasicTest(
       const std::vector<blink::UseCounterFeature>& first_features,
       const std::vector<blink::UseCounterFeature>& second_features = {}) {
     NavigateAndCommit(GURL(kTestUrl));
+
     tester()->SimulateFeaturesUpdate(first_features);
     // Verify that kPageVisits is observed on commit.
     tester()->histogram_tester().ExpectBucketCount(
@@ -48,34 +75,15 @@
         internal::kAnimatedCssPropertiesHistogramName,
         blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
 
-    // TODO (crbug.com/1194678): Make test compatible with features other than
-    // WebFeature.
-    for (const auto& feature : first_features) {
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramMainFrameName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-    }
+    for (const auto& feature : first_features)
+      ExpectBucketCount(feature, 1ul);
 
     tester()->SimulateFeaturesUpdate(second_features);
-    for (const auto& feature : first_features) {
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramMainFrameName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-    }
-    for (const auto& feature : second_features) {
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-      tester()->histogram_tester().ExpectBucketCount(
-          internal::kFeaturesHistogramMainFrameName,
-          static_cast<base::Histogram::Sample>(feature.value()), 1);
-    }
+
+    for (const auto& feature : first_features)
+      ExpectBucketCount(feature, 1ul);
+    for (const auto& feature : second_features)
+      ExpectBucketCount(feature, 1ul);
   }
 
  protected:
@@ -96,8 +104,12 @@
       {
           {blink::mojom::UseCounterFeatureType::kWebFeature, 0},
           {blink::mojom::UseCounterFeatureType::kWebFeature, 1},
+          {blink::mojom::UseCounterFeatureType::kCssProperty, 1},
       },
-      {{blink::mojom::UseCounterFeatureType::kWebFeature, 2}});
+      {
+          {blink::mojom::UseCounterFeatureType::kWebFeature, 2},
+          {blink::mojom::UseCounterFeatureType::kAnimatedCssProperty, 2},
+      });
 }
 
 TEST_F(UseCounterPageLoadMetricsObserverTest, CountDuplicatedFeatures) {
@@ -106,9 +118,14 @@
           {blink::mojom::UseCounterFeatureType::kWebFeature, 0},
           {blink::mojom::UseCounterFeatureType::kWebFeature, 0},
           {blink::mojom::UseCounterFeatureType::kWebFeature, 1},
+          {blink::mojom::UseCounterFeatureType::kCssProperty, 1},
+          {blink::mojom::UseCounterFeatureType::kCssProperty, 1},
+          {blink::mojom::UseCounterFeatureType::kAnimatedCssProperty, 2},
+          {blink::mojom::UseCounterFeatureType::kCssProperty, 3},
       },
       {
           {blink::mojom::UseCounterFeatureType::kWebFeature, 0},
           {blink::mojom::UseCounterFeatureType::kWebFeature, 2},
+          {blink::mojom::UseCounterFeatureType::kAnimatedCssProperty, 2},
       });
 }
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 2a53a640..8c50632 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -491,6 +491,8 @@
     "android_affiliation/mock_affiliation_fetcher_delegate.h",
     "fake_form_fetcher.cc",
     "fake_form_fetcher.h",
+    "mock_biometric_authenticator.cc",
+    "mock_biometric_authenticator.h",
     "mock_bulk_leak_check_service.cc",
     "mock_bulk_leak_check_service.h",
     "mock_password_feature_manager.cc",
diff --git a/components/password_manager/core/browser/biometric_authenticator.h b/components/password_manager/core/browser/biometric_authenticator.h
index 3b1038f..807512a2 100644
--- a/components/password_manager/core/browser/biometric_authenticator.h
+++ b/components/password_manager/core/browser/biometric_authenticator.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BIOMETRIC_AUTHENTICATOR_H_
 
 #include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
 
 namespace password_manager {
 
@@ -24,14 +25,13 @@
 // This interface encapsulates operations related to biometric authentication.
 // It's intended to be used prior to sharing the user's credentials with a
 // website, either via form filling or the Credential Management API.
-class BiometricAuthenticator {
+class BiometricAuthenticator : public base::RefCounted<BiometricAuthenticator> {
  public:
   using AuthenticateCallback = base::OnceCallback<void(bool)>;
 
   BiometricAuthenticator() = default;
   BiometricAuthenticator(const BiometricAuthenticator&) = delete;
   BiometricAuthenticator& operator=(const BiometricAuthenticator&) = delete;
-  virtual ~BiometricAuthenticator() = default;
 
   // Returns whether biometrics are available for a given device. Only if this
   // returns kAvailable, callers can expect Authenticate() to succeed.
@@ -41,6 +41,15 @@
   // |callback| asynchronously on the main thread with the result.
   virtual void Authenticate(const UiCredential& credential,
                             AuthenticateCallback callback) = 0;
+
+  // Cancels an in-progress authentication.
+  virtual void Cancel() = 0;
+
+ protected:
+  virtual ~BiometricAuthenticator() = default;
+
+ private:
+  friend class base::RefCounted<BiometricAuthenticator>;
 };
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/mock_biometric_authenticator.cc b/components/password_manager/core/browser/mock_biometric_authenticator.cc
new file mode 100644
index 0000000..50377d3
--- /dev/null
+++ b/components/password_manager/core/browser/mock_biometric_authenticator.cc
@@ -0,0 +1,12 @@
+// Copyright 2021 The Chromium 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/password_manager/core/browser/mock_biometric_authenticator.h"
+
+namespace password_manager {
+
+MockBiometricAuthenticator::MockBiometricAuthenticator() = default;
+MockBiometricAuthenticator::~MockBiometricAuthenticator() = default;
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/mock_biometric_authenticator.h b/components/password_manager/core/browser/mock_biometric_authenticator.h
new file mode 100644
index 0000000..4960cdb
--- /dev/null
+++ b/components/password_manager/core/browser/mock_biometric_authenticator.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium 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_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BIOMETRIC_AUTHENTICATOR_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BIOMETRIC_AUTHENTICATOR_H_
+
+#include "base/callback.h"
+#include "components/password_manager/core/browser/biometric_authenticator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace password_manager {
+
+// Mocked BiometricAuthenticator used by unit tests.
+class MockBiometricAuthenticator : public BiometricAuthenticator {
+ public:
+  MockBiometricAuthenticator();
+
+  MOCK_METHOD(BiometricsAvailability, CanAuthenticate, (), (override));
+  MOCK_METHOD(void,
+              Authenticate,
+              (const UiCredential&, AuthenticateCallback),
+              (override));
+  MOCK_METHOD(void, Cancel, (), (override));
+
+ private:
+  ~MockBiometricAuthenticator() override;
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BIOMETRIC_AUTHENTICATOR_H_
\ No newline at end of file
diff --git a/components/password_manager/core/browser/password_form.cc b/components/password_manager/core/browser/password_form.cc
index 97311ff0..2d48071 100644
--- a/components/password_manager/core/browser/password_form.cc
+++ b/components/password_manager/core/browser/password_form.cc
@@ -289,7 +289,7 @@
        !it_default_key_values.IsAtEnd(); it_default_key_values.Advance()) {
     const base::Value* actual_value;
     if (form_json.Get(it_default_key_values.key(), &actual_value) &&
-        it_default_key_values.value().Equals(actual_value)) {
+        it_default_key_values.value() == *actual_value) {
       form_json.Remove(it_default_key_values.key(), nullptr);
     }
   }
diff --git a/components/password_manager/core/browser/password_manager_client.cc b/components/password_manager/core/browser/password_manager_client.cc
index dc07e71..c9dd250 100644
--- a/components/password_manager/core/browser/password_manager_client.cc
+++ b/components/password_manager/core/browser/password_manager_client.cc
@@ -6,6 +6,7 @@
 
 #include "base/macros.h"
 #include "components/autofill/core/common/password_generation_util.h"
+#include "components/password_manager/core/browser/biometric_authenticator.h"
 #include "components/password_manager/core/browser/http_auth_manager.h"
 #include "components/password_manager/core/browser/password_form_manager_for_ui.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
@@ -34,7 +35,8 @@
 
 void PasswordManagerClient::OnPasswordSelected(const std::u16string& text) {}
 
-BiometricAuthenticator* PasswordManagerClient::GetBiometricAuthenticator() {
+scoped_refptr<BiometricAuthenticator>
+PasswordManagerClient::GetBiometricAuthenticator() {
   return nullptr;
 }
 
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index 7b2fa60..e5d0849 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -182,7 +182,7 @@
 
   // Returns a pointer to a BiometricAuthenticator. Might be null if
   // BiometricAuthentication is not available for a given platform.
-  virtual BiometricAuthenticator* GetBiometricAuthenticator();
+  virtual scoped_refptr<BiometricAuthenticator> GetBiometricAuthenticator();
 
   // Informs the embedder that the user has requested to generate a
   // password in the focused password field.
diff --git a/components/performance_manager/graph/frame_node_impl.cc b/components/performance_manager/graph/frame_node_impl.cc
index 5e8ee9e..ade4416 100644
--- a/components/performance_manager/graph/frame_node_impl.cc
+++ b/components/performance_manager/graph/frame_node_impl.cc
@@ -53,6 +53,7 @@
 FrameNodeImpl::~FrameNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(child_worker_nodes_.empty());
+  DCHECK(opened_page_nodes_.empty());
   DCHECK(embedded_page_nodes_.empty());
   DCHECK(!execution_context_);
 }
@@ -166,6 +167,11 @@
   return child_frame_nodes_;
 }
 
+const base::flat_set<PageNodeImpl*>& FrameNodeImpl::opened_page_nodes() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return opened_page_nodes_;
+}
+
 const base::flat_set<PageNodeImpl*>& FrameNodeImpl::embedded_page_nodes()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -367,6 +373,28 @@
   return weak_factory_.GetWeakPtr();
 }
 
+void FrameNodeImpl::AddOpenedPage(base::PassKey<PageNodeImpl>,
+                                  PageNodeImpl* page_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(page_node);
+  DCHECK_NE(page_node_, page_node);
+  DCHECK(graph()->NodeInGraph(page_node));
+  DCHECK_EQ(this, page_node->opener_frame_node());
+  bool inserted = opened_page_nodes_.insert(page_node).second;
+  DCHECK(inserted);
+}
+
+void FrameNodeImpl::RemoveOpenedPage(base::PassKey<PageNodeImpl>,
+                                     PageNodeImpl* page_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(page_node);
+  DCHECK_NE(page_node_, page_node);
+  DCHECK(graph()->NodeInGraph(page_node));
+  DCHECK_EQ(this, page_node->opener_frame_node());
+  size_t removed = opened_page_nodes_.erase(page_node);
+  DCHECK_EQ(1u, removed);
+}
+
 void FrameNodeImpl::AddEmbeddedPage(base::PassKey<PageNodeImpl>,
                                     PageNodeImpl* page_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -442,6 +470,22 @@
   return UpcastNodeSet<FrameNode>(child_frame_nodes());
 }
 
+bool FrameNodeImpl::VisitOpenedPageNodes(const PageNodeVisitor& visitor) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto* page_impl : opened_page_nodes()) {
+    const PageNode* page = page_impl;
+    if (!visitor.Run(page))
+      return false;
+  }
+  return true;
+}
+
+const base::flat_set<const PageNode*> FrameNodeImpl::GetOpenedPageNodes()
+    const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return UpcastNodeSet<PageNode>(opened_page_nodes());
+}
+
 bool FrameNodeImpl::VisitEmbeddedPageNodes(
     const PageNodeVisitor& visitor) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -592,8 +636,7 @@
 
   DCHECK(child_frame_nodes_.empty());
 
-  // Sever embedder relationships.
-  SeverEmbeddedPagesAndMaybeReparent();
+  SeverPageRelationshipsAndMaybeReparent();
 
   // Leave the page.
   DCHECK(graph()->NodeInGraph(page_node_));
@@ -619,32 +662,44 @@
   execution_context_.reset();
 }
 
-void FrameNodeImpl::SeverEmbeddedPagesAndMaybeReparent() {
+void FrameNodeImpl::SeverPageRelationshipsAndMaybeReparent() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Copy |embedded_page_nodes_| as we'll be modifying it in this loop: when we
-  // call PageNodeImpl::(Set|Clear)EmbedderFrameNodeAndEmbeddingType() this will
-  // call back into this frame node and call RemoveEmbeddedPage().
-  base::flat_set<PageNodeImpl*> embedded_nodes = embedded_page_nodes_;
-  for (auto* embedded_node : embedded_nodes) {
-    auto embedding_type = embedded_node->embedding_type();
+  // Be careful when iterating: when we call
+  // PageNodeImpl::(Set|Clear)(Opener|Embedder)FrameNode() this will call
+  // back into this frame node and call Remove(Opened|Embedded)Page(), which
+  // modifies |opened_page_nodes_| and |embedded_page_nodes_|.
+  //
+  // We also reparent related pages to this frame's parent to maintain the
+  // relationship between the distinct frame trees for bookkeeping. For the
+  // relationship to be finally severed one of the frame trees must completely
+  // disappear, or it must be explicitly severed (this can happen with
+  // portals).
+  while (!opened_page_nodes_.empty()) {
+    auto* opened_node = *opened_page_nodes_.begin();
+    if (parent_frame_node_) {
+      opened_node->SetOpenerFrameNode(parent_frame_node_);
+    } else {
+      opened_node->ClearOpenerFrameNode();
+    }
+    DCHECK(!base::Contains(opened_page_nodes_, opened_node));
+  }
 
-    // Reparent embedded pages to this frame's parent to maintain the
-    // relationship between the frame trees for bookkeeping. For the
-    // relationship to be finally severed one of the frame trees must completely
-    // disappear, or it must be explicitly severed (this can happen with
-    // portals).
+  while (!embedded_page_nodes_.empty()) {
+    auto* embedded_node = *embedded_page_nodes_.begin();
+    auto embedding_type = embedded_node->embedding_type();
     if (parent_frame_node_) {
       embedded_node->SetEmbedderFrameNodeAndEmbeddingType(parent_frame_node_,
                                                           embedding_type);
     } else {
-      // There's no new parent, so simply clear the embedder.
       embedded_node->ClearEmbedderFrameNodeAndEmbeddingType();
     }
+    DCHECK(!base::Contains(embedded_page_nodes_, embedded_node));
   }
 
   // Expect each page node to have called RemoveEmbeddedPage(), and for this to
   // now be empty.
+  DCHECK(opened_page_nodes_.empty());
   DCHECK(embedded_page_nodes_.empty());
 }
 
diff --git a/components/performance_manager/graph/frame_node_impl.h b/components/performance_manager/graph/frame_node_impl.h
index 169dad8a..c21a099 100644
--- a/components/performance_manager/graph/frame_node_impl.h
+++ b/components/performance_manager/graph/frame_node_impl.h
@@ -108,6 +108,7 @@
 
   // Getters for non-const properties. These are not thread safe.
   const base::flat_set<FrameNodeImpl*>& child_frame_nodes() const;
+  const base::flat_set<PageNodeImpl*>& opened_page_nodes() const;
   const base::flat_set<PageNodeImpl*>& embedded_page_nodes() const;
   LifecycleState lifecycle_state() const;
   bool has_nonempty_beforeunload() const;
@@ -145,12 +146,18 @@
   base::WeakPtr<FrameNodeImpl> GetWeakPtrOnUIThread();
   base::WeakPtr<FrameNodeImpl> GetWeakPtr();
 
-  void SeverEmbeddedPagesAndMaybeReparentForTesting() {
-    SeverEmbeddedPagesAndMaybeReparent();
+  void SeverPageRelationshipsAndMaybeReparentForTesting() {
+    SeverPageRelationshipsAndMaybeReparent();
   }
 
   // Implementation details below this point.
 
+  // Invoked by opened pages when this frame is set/cleared as their opener.
+  // See PageNodeImpl::(Set|Clear)OpenerFrameNode.
+  void AddOpenedPage(base::PassKey<PageNodeImpl> key, PageNodeImpl* page_node);
+  void RemoveOpenedPage(base::PassKey<PageNodeImpl> key,
+                        PageNodeImpl* page_node);
+
   // Invoked by embedded pages when this frame is set/cleared as their embedder.
   // See PageNodeImpl::(Set|Clear)EmbedderFrameNodeAndEmbeddingType.
   void AddEmbeddedPage(base::PassKey<PageNodeImpl> key,
@@ -180,6 +187,8 @@
   int32_t GetSiteInstanceId() const override;
   bool VisitChildFrameNodes(const FrameNodeVisitor& visitor) const override;
   const base::flat_set<const FrameNode*> GetChildFrameNodes() const override;
+  bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const override;
+  const base::flat_set<const PageNode*> GetOpenedPageNodes() const override;
   bool VisitEmbeddedPageNodes(const PageNodeVisitor& visitor) const override;
   const base::flat_set<const PageNode*> GetEmbeddedPageNodes() const override;
   LifecycleState GetLifecycleState() const override;
@@ -237,11 +246,11 @@
   void OnBeforeLeavingGraph() override;
   void RemoveNodeAttachedData() override;
 
-  // Helper function to sever all embedded page relationships. This is called
-  // before destroying the frame node in "OnBeforeLeavingGraph". Note that this
-  // will reparent embedded pages to this frame's parent so that tracking is
-  // maintained.
-  void SeverEmbeddedPagesAndMaybeReparent();
+  // Helper function to sever all opened/embedded page relationships. This is
+  // called before destroying the frame node in "OnBeforeLeavingGraph". Note
+  // that this will reparent embedded pages to this frame's parent so that
+  // tracking is maintained.
+  void SeverPageRelationshipsAndMaybeReparent();
 
   // This is not quite the same as GetMainFrame, because there can be multiple
   // main frames while the main frame is navigating. This explicitly walks up
@@ -287,6 +296,9 @@
 
   base::flat_set<FrameNodeImpl*> child_frame_nodes_;
 
+  // The set of pages that have been opened by this frame.
+  base::flat_set<PageNodeImpl*> opened_page_nodes_;
+
   // The set of pages that have been embedded by this frame.
   base::flat_set<PageNodeImpl*> embedded_page_nodes_;
 
diff --git a/components/performance_manager/graph/frame_node_impl_unittest.cc b/components/performance_manager/graph/frame_node_impl_unittest.cc
index 375d9d51..0aad9309 100644
--- a/components/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/components/performance_manager/graph/frame_node_impl_unittest.cc
@@ -573,6 +573,8 @@
 
   // Note that embedder functionality is actually tested in the
   // performance_manager_browsertest.
+  MOCK_METHOD2(OnOpenerFrameNodeChanged,
+               void(const PageNode*, const FrameNode*));
   MOCK_METHOD3(OnEmbedderFrameNodeChanged,
                void(const PageNode*, const FrameNode*, EmbeddingType));
 };
@@ -581,7 +583,7 @@
 
 }  // namespace
 
-TEST_F(FrameNodeImplTest, EmbedderRelationships) {
+TEST_F(FrameNodeImplTest, PageRelationships) {
   using EmbeddingType = PageNode::EmbeddingType;
 
   auto process = CreateNode<ProcessNodeImpl>();
@@ -603,14 +605,14 @@
 
   // You can always call the pre-delete embedder clearing helper, even if you
   // have no such relationships.
-  frameB1->SeverEmbeddedPagesAndMaybeReparentForTesting();
+  frameB1->SeverPageRelationshipsAndMaybeReparentForTesting();
 
   // You can't clear an embedder if you don't already have one.
   EXPECT_DCHECK_DEATH(pageB->ClearEmbedderFrameNodeAndEmbeddingType());
 
   // You can't be an embedder for your own frame tree.
   EXPECT_DCHECK_DEATH(pageA->SetEmbedderFrameNodeAndEmbeddingType(
-      frameA1.get(), EmbeddingType::kPopup));
+      frameA1.get(), EmbeddingType::kPortal));
 
   // You can't set a null embedder or an invalid embedded type.
   EXPECT_DCHECK_DEATH(pageB->SetEmbedderFrameNodeAndEmbeddingType(
@@ -640,18 +642,16 @@
   EXPECT_EQ(1u, pframeA1->GetEmbeddedPageNodes().count(pageB.get()));
   testing::Mock::VerifyAndClear(&obs);
 
-  // Set another embedder relationship.
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageC.get(), nullptr,
-                                              EmbeddingType::kInvalid));
-  pageC->SetEmbedderFrameNodeAndEmbeddingType(frameA1.get(),
-                                              EmbeddingType::kPopup);
-  EXPECT_EQ(frameA1.get(), pageC->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kPopup, pageC->embedding_type());
-  EXPECT_EQ(2u, frameA1->embedded_page_nodes().size());
+  // Set an opener relationship.
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), nullptr));
+  pageC->SetOpenerFrameNode(frameA1.get());
+  EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
+  EXPECT_EQ(1u, frameA1->embedded_page_nodes().size());
+  EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
   EXPECT_EQ(1u, frameA1->embedded_page_nodes().count(pageB.get()));
   testing::Mock::VerifyAndClear(&obs);
 
-  // Do a traversal.
+  // Do an embedded page traversal.
   std::set<const PageNode*> visited;
   EXPECT_TRUE(
       ToPublic(frameA1.get())
@@ -661,8 +661,19 @@
                 return true;
               },
               base::Unretained(&visited))));
-  EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageB.get()),
-                                                     ToPublic(pageC.get())));
+  EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageB.get())));
+
+  // Do an opened page traversal.
+  visited.clear();
+  EXPECT_TRUE(
+      ToPublic(frameA1.get())
+          ->VisitOpenedPageNodes(base::BindRepeating(
+              [](std::set<const PageNode*>* visited, const PageNode* page) {
+                EXPECT_TRUE(visited->insert(page).second);
+                return true;
+              },
+              base::Unretained(&visited))));
+  EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageC.get())));
 
   // Do an aborted visit.
   visited.clear();
@@ -676,80 +687,69 @@
               base::Unretained(&visited))));
   EXPECT_EQ(1u, visited.size());
 
-  // Manually clear the first relationship (initiated from the page).
+  // Manually clear the embedder relationship (initiated from the page).
   EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), frameA1.get(),
                                               EmbeddingType::kGuestView));
   pageB->ClearEmbedderFrameNodeAndEmbeddingType();
   EXPECT_EQ(nullptr, pageB->embedder_frame_node());
   EXPECT_EQ(EmbeddingType::kInvalid, pageB->embedding_type());
-  EXPECT_EQ(frameA1.get(), pageC->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kPopup, pageC->embedding_type());
-  EXPECT_EQ(1u, frameA1->embedded_page_nodes().size());
-  EXPECT_EQ(0u, frameA1->embedded_page_nodes().count(pageB.get()));
+  EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
+  EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
   testing::Mock::VerifyAndClear(&obs);
 
-  // Clear the second relationship (initiated from the frame).
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageC.get(), frameA1.get(),
-                                              EmbeddingType::kPopup));
-  frameA1->SeverEmbeddedPagesAndMaybeReparentForTesting();
+  // Clear the opener relationship.
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), frameA1.get()));
+  frameA1->SeverPageRelationshipsAndMaybeReparentForTesting();
   EXPECT_EQ(nullptr, pageC->embedder_frame_node());
   EXPECT_EQ(EmbeddingType::kInvalid, pageC->embedding_type());
+  EXPECT_TRUE(frameA1->opened_page_nodes().empty());
   EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
   testing::Mock::VerifyAndClear(&obs);
 
-  // Set a popup embedder relationship on node A2.
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), nullptr,
-                                              EmbeddingType::kInvalid));
-  pageB->SetEmbedderFrameNodeAndEmbeddingType(frameA2.get(),
-                                              EmbeddingType::kPopup);
-  EXPECT_EQ(frameA2.get(), pageB->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kPopup, pageB->embedding_type());
+  // Set a popup relationship on node A2.
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr));
+  pageB->SetOpenerFrameNode(frameA2.get());
+  EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
+  EXPECT_TRUE(frameA1->opened_page_nodes().empty());
   EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
-  EXPECT_EQ(1u, frameA2->embedded_page_nodes().size());
-  EXPECT_EQ(1u, frameA2->embedded_page_nodes().count(pageB.get()));
+  EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
+  EXPECT_TRUE(frameA2->embedded_page_nodes().empty());
+  EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
   testing::Mock::VerifyAndClear(&obs);
 
   // Clear it with the helper, and expect it to be reparented to node A1.
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), frameA2.get(),
-                                              EmbeddingType::kPopup));
-  frameA2->SeverEmbeddedPagesAndMaybeReparentForTesting();
-  EXPECT_EQ(frameA1.get(), pageB->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kPopup, pageB->embedding_type());
-  EXPECT_EQ(1u, frameA1->embedded_page_nodes().size());
-  EXPECT_EQ(1u, frameA1->embedded_page_nodes().count(pageB.get()));
-  EXPECT_TRUE(frameA2->embedded_page_nodes().empty());
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get()));
+  frameA2->SeverPageRelationshipsAndMaybeReparentForTesting();
+  EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
+  EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
+  EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
+  EXPECT_TRUE(frameA2->opened_page_nodes().empty());
   testing::Mock::VerifyAndClear(&obs);
 
   // Clear it again with the helper. This time reparenting can't happen, as it
   // was already parented to the root.
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), frameA1.get(),
-                                              EmbeddingType::kPopup));
-  frameA1->SeverEmbeddedPagesAndMaybeReparentForTesting();
-  EXPECT_EQ(nullptr, pageB->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kInvalid, pageB->embedding_type());
-  EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
-  EXPECT_TRUE(frameA2->embedded_page_nodes().empty());
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA1.get()));
+  frameA1->SeverPageRelationshipsAndMaybeReparentForTesting();
+  EXPECT_EQ(nullptr, pageB->opener_frame_node());
+  EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+  EXPECT_TRUE(frameA2->opened_page_nodes().empty());
   testing::Mock::VerifyAndClear(&obs);
 
   // verify that the embedder relationship is torn down before any node removal
   // notification arrives.
-  EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), nullptr,
-                                              EmbeddingType::kInvalid));
-  pageB->SetEmbedderFrameNodeAndEmbeddingType(frameA2.get(),
-                                              EmbeddingType::kPopup);
-  EXPECT_EQ(frameA2.get(), pageB->embedder_frame_node());
-  EXPECT_EQ(EmbeddingType::kPopup, pageB->embedding_type());
-  EXPECT_TRUE(frameA1->embedded_page_nodes().empty());
-  EXPECT_EQ(1u, frameA2->embedded_page_nodes().size());
-  EXPECT_EQ(1u, frameA2->embedded_page_nodes().count(pageB.get()));
+  EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr));
+  pageB->SetOpenerFrameNode(frameA2.get());
+  EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
+  EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+  EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
+  EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
   testing::Mock::VerifyAndClear(&obs);
 
   {
     ::testing::InSequence seq;
 
     // These must occur in sequence.
-    EXPECT_CALL(obs, OnEmbedderFrameNodeChanged(pageB.get(), frameA2.get(),
-                                                EmbeddingType::kPopup));
+    EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get()));
     EXPECT_CALL(obs, OnBeforePageNodeRemoved(pageB.get()));
   }
   frameB1.reset();
diff --git a/components/performance_manager/graph/graph_impl_unittest.cc b/components/performance_manager/graph/graph_impl_unittest.cc
index d6ff43a..4493444 100644
--- a/components/performance_manager/graph/graph_impl_unittest.cc
+++ b/components/performance_manager/graph/graph_impl_unittest.cc
@@ -322,7 +322,7 @@
   EXPECT_EQ(0u, descr.DictSize());
 }
 
-TEST_F(GraphImplTest, EmbeddersClearedOnTeardown) {
+TEST_F(GraphImplTest, OpenersAndEmbeddersClearedOnTeardown) {
   auto process = CreateNode<ProcessNodeImpl>();
   auto pageA = CreateNode<PageNodeImpl>();
   auto frameA1 = CreateFrameNodeAutoId(process.get(), pageA.get());
@@ -338,8 +338,7 @@
   // will explode.
   pageB->SetEmbedderFrameNodeAndEmbeddingType(
       frameA1.get(), PageNode::EmbeddingType::kGuestView);
-  pageC->SetEmbedderFrameNodeAndEmbeddingType(frameA2.get(),
-                                              PageNode::EmbeddingType::kPopup);
+  pageC->SetOpenerFrameNode(frameA2.get());
 }
 
 }  // namespace performance_manager
diff --git a/components/performance_manager/graph/page_node.cc b/components/performance_manager/graph/page_node.cc
index e3be409..76a97e8 100644
--- a/components/performance_manager/graph/page_node.cc
+++ b/components/performance_manager/graph/page_node.cc
@@ -13,8 +13,6 @@
   switch (embedding_type) {
     case PageNode::EmbeddingType::kInvalid:
       return "kInvalid";
-    case PageNode::EmbeddingType::kPopup:
-      return "kPopup";
     case PageNode::EmbeddingType::kGuestView:
       return "kGuestView";
     case PageNode::EmbeddingType::kPortal:
diff --git a/components/performance_manager/graph/page_node_impl.cc b/components/performance_manager/graph/page_node_impl.cc
index d2b4701..1a9f524 100644
--- a/components/performance_manager/graph/page_node_impl.cc
+++ b/components/performance_manager/graph/page_node_impl.cc
@@ -36,6 +36,7 @@
 
 PageNodeImpl::~PageNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(nullptr, opener_frame_node_);
   DCHECK_EQ(nullptr, embedder_frame_node_);
   DCHECK_EQ(EmbeddingType::kInvalid, embedding_type_);
   DCHECK(!page_load_tracker_data_);
@@ -164,6 +165,11 @@
   return *main_frame_nodes_.begin();
 }
 
+FrameNodeImpl* PageNodeImpl::opener_frame_node() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return opener_frame_node_;
+}
+
 FrameNodeImpl* PageNodeImpl::embedder_frame_node() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(embedder_frame_node_ || embedding_type_ == EmbeddingType::kInvalid);
@@ -257,6 +263,35 @@
   return freezing_vote_.value();
 }
 
+void PageNodeImpl::SetOpenerFrameNode(FrameNodeImpl* opener) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(opener);
+  DCHECK(graph()->NodeInGraph(opener));
+  DCHECK_NE(this, opener->page_node());
+
+  auto* previous_opener = opener_frame_node_;
+  if (previous_opener)
+    previous_opener->RemoveOpenedPage(PassKey(), this);
+  opener_frame_node_ = opener;
+  opener->AddOpenedPage(PassKey(), this);
+
+  for (auto* observer : GetObservers())
+    observer->OnOpenerFrameNodeChanged(this, previous_opener);
+}
+
+void PageNodeImpl::ClearOpenerFrameNode() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_NE(nullptr, opener_frame_node_);
+
+  auto* previous_opener = opener_frame_node_;
+
+  opener_frame_node_->RemoveOpenedPage(PassKey(), this);
+  opener_frame_node_ = nullptr;
+
+  for (auto* observer : GetObservers())
+    observer->OnOpenerFrameNodeChanged(this, previous_opener);
+}
+
 void PageNodeImpl::SetEmbedderFrameNodeAndEmbeddingType(
     FrameNodeImpl* embedder,
     EmbeddingType embedding_type) {
@@ -334,6 +369,10 @@
 void PageNodeImpl::OnBeforeLeavingGraph() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Sever opener relationships.
+  if (opener_frame_node_)
+    ClearOpenerFrameNode();
+
   // Sever embedder relationships.
   if (embedder_frame_node_)
     ClearEmbedderFrameNodeAndEmbeddingType();
@@ -354,6 +393,11 @@
   return browser_context_id();
 }
 
+const FrameNode* PageNodeImpl::GetOpenerFrameNode() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return opener_frame_node();
+}
+
 const FrameNode* PageNodeImpl::GetEmbedderFrameNode() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return embedder_frame_node();
diff --git a/components/performance_manager/graph/page_node_impl.h b/components/performance_manager/graph/page_node_impl.h
index 020a4cd..0069d359 100644
--- a/components/performance_manager/graph/page_node_impl.h
+++ b/components/performance_manager/graph/page_node_impl.h
@@ -83,6 +83,7 @@
 
   // Accessors.
   const std::string& browser_context_id() const;
+  FrameNodeImpl* opener_frame_node() const;
   FrameNodeImpl* embedder_frame_node() const;
   EmbeddingType embedding_type() const;
   bool is_visible() const;
@@ -101,6 +102,10 @@
   bool had_form_interaction() const;
   const base::Optional<freezing::FreezingVote>& freezing_vote() const;
 
+  // Invoked to set/clear the opener of this page.
+  void SetOpenerFrameNode(FrameNodeImpl* opener);
+  void ClearOpenerFrameNode();
+
   // Invoked to set/clear the embedder of this page.
   void SetEmbedderFrameNodeAndEmbeddingType(FrameNodeImpl* embedder,
                                             EmbeddingType embedder_type);
@@ -185,6 +190,7 @@
 
   // PageNode implementation.
   const std::string& GetBrowserContextID() const override;
+  const FrameNode* GetOpenerFrameNode() const override;
   const FrameNode* GetEmbedderFrameNode() const override;
   EmbeddingType GetEmbeddingType() const override;
   bool IsVisible() const override;
@@ -268,6 +274,10 @@
   // The unique ID of the browser context that this page belongs to.
   const std::string browser_context_id_;
 
+  // The opener of this page, if there is one.
+  FrameNodeImpl* opener_frame_node_ GUARDED_BY_CONTEXT(sequence_checker_) =
+      nullptr;
+
   // The embedder of this page, if there is one.
   FrameNodeImpl* embedder_frame_node_ GUARDED_BY_CONTEXT(sequence_checker_) =
       nullptr;
diff --git a/components/performance_manager/graph/page_node_impl_unittest.cc b/components/performance_manager/graph/page_node_impl_unittest.cc
index 966f4715..7f69a88 100644
--- a/components/performance_manager/graph/page_node_impl_unittest.cc
+++ b/components/performance_manager/graph/page_node_impl_unittest.cc
@@ -224,8 +224,10 @@
 
   MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
   MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
-  // Note that embedder functionality is actually tested in the FrameNodeImpl
-  // and GraphImpl unittests.
+  // Note that opener/embedder functionality is actually tested in the
+  // FrameNodeImpl and GraphImpl unittests.
+  MOCK_METHOD2(OnOpenerFrameNodeChanged,
+               void(const PageNode*, const FrameNode*));
   MOCK_METHOD3(OnEmbedderFrameNodeChanged,
                void(const PageNode*, const FrameNode*, EmbeddingType));
   MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
diff --git a/components/performance_manager/performance_manager_browsertest.cc b/components/performance_manager/performance_manager_browsertest.cc
index 34bec7c..fd539cf 100644
--- a/components/performance_manager/performance_manager_browsertest.cc
+++ b/components/performance_manager/performance_manager_browsertest.cc
@@ -85,8 +85,7 @@
   run_loop_after_contents_reset.Run();
 }
 
-IN_PROC_BROWSER_TEST_F(PerformanceManagerBrowserTest,
-                       PopupEmbedderTrackingWorks) {
+IN_PROC_BROWSER_TEST_F(PerformanceManagerBrowserTest, OpenerTrackingWorks) {
   // Load a page that will load a popup.
   GURL url(embedded_test_server()->GetURL("a.com", "/a_popup_a.html"));
   content::ShellAddedObserver shell_added_observer;
@@ -106,11 +105,9 @@
       FROM_HERE, base::BindLambdaForTesting([&page, &run_loop]() {
         EXPECT_TRUE(page);
         auto* frame = page->GetMainFrameNode();
-        EXPECT_EQ(1u, frame->GetEmbeddedPageNodes().size());
-        auto* embedded_page = *(frame->GetEmbeddedPageNodes().begin());
-        EXPECT_EQ(PageNode::EmbeddingType::kPopup,
-                  embedded_page->GetEmbeddingType());
-        EXPECT_EQ(frame, embedded_page->GetEmbedderFrameNode());
+        EXPECT_EQ(1u, frame->GetOpenedPageNodes().size());
+        auto* embedded_page = *(frame->GetOpenedPageNodes().begin());
+        EXPECT_EQ(frame, embedded_page->GetOpenerFrameNode());
         run_loop.Quit();
       }));
   run_loop.Run();
diff --git a/components/performance_manager/performance_manager_tab_helper.cc b/components/performance_manager/performance_manager_tab_helper.cc
index 080f78c..88801cb4 100644
--- a/components/performance_manager/performance_manager_tab_helper.cc
+++ b/components/performance_manager/performance_manager_tab_helper.cc
@@ -64,11 +64,9 @@
     return false;
 
   PerformanceManagerImpl::CallOnGraphImpl(
-      FROM_HERE,
-      base::BindOnce(&PageNodeImpl::SetEmbedderFrameNodeAndEmbeddingType,
-                     base::Unretained(helper->page_node()),
-                     base::Unretained(opener_frame_node),
-                     PageNode::EmbeddingType::kPopup));
+      FROM_HERE, base::BindOnce(&PageNodeImpl::SetOpenerFrameNode,
+                                base::Unretained(helper->page_node()),
+                                base::Unretained(opener_frame_node)));
   return true;
 }
 
@@ -388,11 +386,7 @@
   } else {
     embedding_type = PageNode::EmbeddingType::kGuestView;
     // For a guest view, the RFH should already have been seen.
-
     // Note that guest views can simultaneously have openers *and* be embedded.
-    // The embedded relationship has higher priority, but we'll fall back to
-    // using the window.open relationship if the embedded relationship is
-    // severed.
   }
   DCHECK_NE(PageNode::EmbeddingType::kInvalid, embedding_type);
   if (!frame) {
@@ -417,15 +411,10 @@
     content::WebContents* inner_web_contents) {
   auto* helper = FromWebContents(inner_web_contents);
   DCHECK(helper);
-
-  // Fall back to using the window.open opener if it exists. If not, simply
-  // clear the opener relationship entirely.
-  if (!ConnectWindowOpenRelationshipIfExists(helper, inner_web_contents)) {
-    PerformanceManagerImpl::CallOnGraphImpl(
-        FROM_HERE,
-        base::BindOnce(&PageNodeImpl::ClearEmbedderFrameNodeAndEmbeddingType,
-                       base::Unretained(helper->page_node())));
-  }
+  PerformanceManagerImpl::CallOnGraphImpl(
+      FROM_HERE,
+      base::BindOnce(&PageNodeImpl::ClearEmbedderFrameNodeAndEmbeddingType,
+                     base::Unretained(helper->page_node())));
 }
 
 void PerformanceManagerTabHelper::WebContentsDestroyed() {
diff --git a/components/performance_manager/public/graph/frame_node.h b/components/performance_manager/public/graph/frame_node.h
index 7d1d620f..579cc89 100644
--- a/components/performance_manager/public/graph/frame_node.h
+++ b/components/performance_manager/public/graph/frame_node.h
@@ -119,6 +119,17 @@
   // VisitChildFrameNodes when that makes sense.
   virtual const base::flat_set<const FrameNode*> GetChildFrameNodes() const = 0;
 
+  // Visits the page nodes that have been opened by this frame. The iteration
+  // is halted if the visitor returns false. Returns true if every call to the
+  // visitor returned true, false otherwise.
+  virtual bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const = 0;
+
+  // Returns the set of opened pages associatted with this frame. Note that
+  // this incurs a full container copy all the opened nodes. Please use
+  // VisitOpenedPageNodes when that makes sense. This can change over the
+  // lifetime of the frame.
+  virtual const base::flat_set<const PageNode*> GetOpenedPageNodes() const = 0;
+
   // Visits the page nodes that have been embedded by this frame. The iteration
   // is halted if the visitor returns false. Returns true if every call to the
   // visitor returned true, false otherwise.
diff --git a/components/performance_manager/public/graph/page_node.h b/components/performance_manager/public/graph/page_node.h
index cd2fb09..7db964eb 100644
--- a/components/performance_manager/public/graph/page_node.h
+++ b/components/performance_manager/public/graph/page_node.h
@@ -40,8 +40,6 @@
   enum class EmbeddingType {
     // Returned if this node doesn't have an embedder.
     kInvalid,
-    // This page is a popup (the embedder created it via window.open).
-    kPopup,
     // This page is a guest view. This can be many things (<webview>, <appview>,
     // etc) but is backed by the same inner/outer WebContents mechanism.
     kGuestView,
@@ -82,6 +80,10 @@
   // Returns the unique ID of the browser context that this page belongs to.
   virtual const std::string& GetBrowserContextID() const = 0;
 
+  // Returns the opener frame node, if there is one. This may change over the
+  // lifetime of this page. See "OnOpenerFrameNodeChanged".
+  virtual const FrameNode* GetOpenerFrameNode() const = 0;
+
   // Returns the embedder frame node, if there is one. This may change over the
   // lifetime of this page. See "OnEmbedderFrameNodeChanged".
   virtual const FrameNode* GetEmbedderFrameNode() const = 0;
@@ -204,9 +206,16 @@
 
   // Notifications of property changes.
 
+  // Invoked when this page has been assigned an opener, had the opener
+  // change, or had the opener removed. This happens when a page is opened
+  // via window.open, or when that relationship is subsequently severed or
+  // reparented.
+  virtual void OnOpenerFrameNodeChanged(const PageNode* page_node,
+                                        const FrameNode* previous_opener) = 0;
+
   // Invoked when this page has been assigned an embedder, had the embedder
   // change, or had the embedder removed. This can happen if a page is opened
-  // via window.open, webviews, portals, etc, or when that relationship is
+  // via webviews, guestviews, portals, etc, or when that relationship is
   // subsequently severed or reparented.
   virtual void OnEmbedderFrameNodeChanged(
       const PageNode* page_node,
@@ -276,6 +285,8 @@
   // PageNodeObserver implementation:
   void OnPageNodeAdded(const PageNode* page_node) override {}
   void OnBeforePageNodeRemoved(const PageNode* page_node) override {}
+  void OnOpenerFrameNodeChanged(const PageNode* page_node,
+                                const FrameNode* previous_opener) override {}
   void OnEmbedderFrameNodeChanged(
       const PageNode* page_node,
       const FrameNode* previous_embedder,
diff --git a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
index f379182..c11ceab 100644
--- a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
+++ b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
@@ -254,8 +254,7 @@
   if (opener) {
     pages_.push_back(CreateNode<PageNodeImpl>());
     page = pages_.back().get();
-    page->SetEmbedderFrameNodeAndEmbeddingType(opener,
-                                               PageNode::EmbeddingType::kPopup);
+    page->SetOpenerFrameNode(opener);
   }
 
   int frame_tree_node_id = GetNextUniqueId();
diff --git a/components/power_scheduler/power_mode.cc b/components/power_scheduler/power_mode.cc
index 1283cd45..be6ea6b0 100644
--- a/components/power_scheduler/power_mode.cc
+++ b/components/power_scheduler/power_mode.cc
@@ -22,6 +22,8 @@
       return "Loading";
     case PowerMode::kAnimation:
       return "Animation";
+    case PowerMode::kLoadingAnimation:
+      return "LoadingAnimation";
     case PowerMode::kResponse:
       return "Response";
     case PowerMode::kNonWebActivity:
diff --git a/components/power_scheduler/power_mode.h b/components/power_scheduler/power_mode.h
index b9ddb6d9..644e703 100644
--- a/components/power_scheduler/power_mode.h
+++ b/components/power_scheduler/power_mode.h
@@ -34,6 +34,9 @@
   // A surface rendered by the process is animating and producing frames.
   kAnimation,
 
+  // Both kLoading + kAnimation modes are active.
+  kLoadingAnimation,
+
   // The process is responding to user input.
   kResponse,
 
diff --git a/components/power_scheduler/power_mode_arbiter.cc b/components/power_scheduler/power_mode_arbiter.cc
index 87c0254d6..bc0fb94 100644
--- a/components/power_scheduler/power_mode_arbiter.cc
+++ b/components/power_scheduler/power_mode_arbiter.cc
@@ -308,6 +308,7 @@
 PowerMode PowerModeArbiter::ComputeActiveModeLocked() {
   PowerMode mode = PowerMode::kIdle;
   bool is_audible = false;
+  bool is_loading = false;
 
   for (const auto& voter_and_vote : votes_) {
     PowerMode vote = voter_and_vote.second.mode();
@@ -315,12 +316,18 @@
       mode = vote;
     if (vote == PowerMode::kAudible)
       is_audible = true;
+    if (vote == PowerMode::kLoading)
+      is_loading = true;
   }
 
   // In background, audible overrides.
   if (mode == PowerMode::kBackground && is_audible)
     return PowerMode::kAudible;
 
+  // Break out loading while concurrently animating into a separate mode.
+  if (mode == PowerMode::kAnimation && is_loading)
+    return PowerMode::kLoadingAnimation;
+
   return mode;
 }
 
diff --git a/components/power_scheduler/power_mode_arbiter_unittest.cc b/components/power_scheduler/power_mode_arbiter_unittest.cc
index 3b4349e..3bfa784 100644
--- a/components/power_scheduler/power_mode_arbiter_unittest.cc
+++ b/components/power_scheduler/power_mode_arbiter_unittest.cc
@@ -101,8 +101,9 @@
                   PowerMode::kAnimation);
   vote_and_expect(PowerMode::kAnimation, PowerMode::kAudible,
                   PowerMode::kAnimation);
+  // Animation while loading breaks out into a separate mode.
   vote_and_expect(PowerMode::kAnimation, PowerMode::kLoading,
-                  PowerMode::kAnimation);
+                  PowerMode::kLoadingAnimation);
 
   // Loading trumps remaining modes.
   vote_and_expect(PowerMode::kLoading, PowerMode::kIdle, PowerMode::kLoading);
diff --git a/components/services/storage/BUILD.gn b/components/services/storage/BUILD.gn
index fdb075e..3d540f5 100644
--- a/components/services/storage/BUILD.gn
+++ b/components/services/storage/BUILD.gn
@@ -180,6 +180,7 @@
     "//components/services/storage/public/cpp:tests",
     "//components/services/storage/public/cpp/filesystem:tests",
     "//components/services/storage/public/mojom",
+    "//components/services/storage/public/mojom:tests",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//net",
diff --git a/components/services/storage/public/cpp/BUILD.gn b/components/services/storage/public/cpp/BUILD.gn
index 40990fa7..774d9e7 100644
--- a/components/services/storage/public/cpp/BUILD.gn
+++ b/components/services/storage/public/cpp/BUILD.gn
@@ -8,30 +8,43 @@
   public = [
     "constants.h",
     "quota_client_callback_wrapper.h",
-    "storage_key.h",
   ]
 
   sources = [
     "constants.cc",
     "quota_client_callback_wrapper.cc",
-    "storage_key.cc",
   ]
 
   public_deps = [
+    ":storage_key",
     "//base",
     "//components/services/storage/public/cpp/filesystem",
     "//components/services/storage/public/mojom",
-    "//url",
   ]
 
   defines = [ "IS_STORAGE_SERVICE_PUBLIC_IMPL" ]
 }
 
+component("storage_key") {
+  output_name = "storage_service_storage_key_support"
+
+  public = [ "storage_key.h" ]
+
+  sources = [ "storage_key.cc" ]
+
+  public_deps = [
+    "//base",
+    "//url",
+  ]
+
+  defines = [ "IS_STORAGE_SERVICE_STORAGE_KEY_SUPPORT_IMPL" ]
+}
+
 source_set("tests") {
   testonly = true
   sources = [ "storage_key_unittest.cc" ]
   deps = [
-    ":cpp",
+    ":storage_key",
     "//testing/gtest",
   ]
 }
diff --git a/components/services/storage/public/cpp/storage_key.h b/components/services/storage/public/cpp/storage_key.h
index e79b5e7..ca26e4d2 100644
--- a/components/services/storage/public/cpp/storage_key.h
+++ b/components/services/storage/public/cpp/storage_key.h
@@ -7,12 +7,13 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "url/origin.h"
 
 namespace storage {
 
 // A class representing the key that Storage APIs use to key their storage on.
-class COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) StorageKey {
+class COMPONENT_EXPORT(STORAGE_SERVICE_STORAGE_KEY_SUPPORT) StorageKey {
  public:
   StorageKey() = default;
   explicit StorageKey(const url::Origin& origin) : origin_(origin) {}
@@ -41,15 +42,15 @@
   const url::Origin& origin() const { return origin_; }
 
  private:
-  COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC)
+  COMPONENT_EXPORT(STORAGE_SERVICE_STORAGE_KEY_SUPPORT)
   friend bool operator==(const StorageKey& lhs, const StorageKey& rhs);
 
-  COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC)
+  COMPONENT_EXPORT(STORAGE_SERVICE_STORAGE_KEY_SUPPORT)
   friend bool operator!=(const StorageKey& lhs, const StorageKey& rhs);
 
   // Allows StorageKey to be used as a key in STL (for example, a std::set or
   // std::map).
-  COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC)
+  COMPONENT_EXPORT(STORAGE_SERVICE_STORAGE_KEY_SUPPORT)
   friend bool operator<(const StorageKey& lhs, const StorageKey& rhs);
 
   url::Origin origin_;
diff --git a/components/services/storage/public/mojom/BUILD.gn b/components/services/storage/public/mojom/BUILD.gn
index df4c03b..58eeb06 100644
--- a/components/services/storage/public/mojom/BUILD.gn
+++ b/components/services/storage/public/mojom/BUILD.gn
@@ -25,6 +25,7 @@
 
   public_deps = [
     "//components/services/storage/public/mojom/filesystem",
+    "//components/services/storage/public/mojom/storage_key",
     "//mojo/public/mojom/base",
     "//services/network/public/mojom",
     "//third_party/blink/public/mojom:mojom_core",
@@ -49,3 +50,15 @@
   testonly = true
   sources = [ "test_api.test-mojom" ]
 }
+
+source_set("tests") {
+  testonly = true
+  sources = [ "storage_key/storage_key_mojom_traits_unittest.cc" ]
+  deps = [
+    "//components/services/storage/public/cpp:storage_key",
+    "//components/services/storage/public/mojom/storage_key",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/test_support:test_utils",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/services/storage/public/mojom/storage_key/BUILD.gn b/components/services/storage/public/mojom/storage_key/BUILD.gn
new file mode 100644
index 0000000..18d72f7
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2021 The Chromium 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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("storage_key") {
+  sources = [ "storage_key.mojom" ]
+
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_origin",
+  ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "storage.mojom.StorageKey"
+          cpp = "::storage::StorageKey"
+        },
+      ]
+      traits_headers = [ "storage_key_mojom_traits.h" ]
+      traits_sources = [ "storage_key_mojom_traits.cc" ]
+      traits_public_deps = [
+        "//components/services/storage/public/cpp:storage_key",
+        "//url/mojom:url_mojom_origin",
+      ]
+    },
+  ]
+}
diff --git a/components/services/storage/public/mojom/storage_key/OWNERS b/components/services/storage/public/mojom/storage_key/OWNERS
new file mode 100644
index 0000000..d970307
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/OWNERS
@@ -0,0 +1,4 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
\ No newline at end of file
diff --git a/components/services/storage/public/mojom/storage_key/storage_key.mojom b/components/services/storage/public/mojom/storage_key/storage_key.mojom
new file mode 100644
index 0000000..2694331
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/storage_key.mojom
@@ -0,0 +1,12 @@
+// Copyright 2021 The Chromium 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 storage.mojom;
+
+import "url/mojom/origin.mojom";
+
+// Represents a storage::StorageKey (components/services/storage/public/cpp).
+struct StorageKey {
+  url.mojom.Origin origin;
+};
diff --git a/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.cc b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.cc
new file mode 100644
index 0000000..2e0a95d
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.cc
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium 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/services/storage/public/mojom/storage_key/storage_key_mojom_traits.h"
+
+#include "url/origin.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<storage::mojom::StorageKeyDataView, storage::StorageKey>::
+    Read(storage::mojom::StorageKeyDataView data, storage::StorageKey* out) {
+  url::Origin origin;
+  if (!data.ReadOrigin(&origin))
+    return false;
+
+  *out = storage::StorageKey(origin);
+  return true;
+}
+
+}  // namespace mojo
diff --git a/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.h b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.h
new file mode 100644
index 0000000..61452e1
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium 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_SERVICES_STORAGE_PUBLIC_MOJOM_STORAGE_KEY_STORAGE_KEY_MOJOM_TRAITS_H_
+#define COMPONENTS_SERVICES_STORAGE_PUBLIC_MOJOM_STORAGE_KEY_STORAGE_KEY_MOJOM_TRAITS_H_
+
+#include "components/services/storage/public/cpp/storage_key.h"
+#include "components/services/storage/public/mojom/storage_key/storage_key.mojom.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+
+namespace url {
+class Origin;
+}  // namespace url
+
+namespace mojo {
+
+template <>
+class StructTraits<storage::mojom::StorageKeyDataView, storage::StorageKey> {
+ public:
+  static const url::Origin& origin(const storage::StorageKey& key) {
+    return key.origin();
+  }
+
+  static bool Read(storage::mojom::StorageKeyDataView data,
+                   storage::StorageKey* out);
+};
+
+}  // namespace mojo
+
+#endif  // COMPONENTS_SERVICES_STORAGE_PUBLIC_MOJOM_STORAGE_KEY_STORAGE_KEY_MOJOM_TRAITS_H_
diff --git a/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits_unittest.cc b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits_unittest.cc
new file mode 100644
index 0000000..2cc8506
--- /dev/null
+++ b/components/services/storage/public/mojom/storage_key/storage_key_mojom_traits_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright 2021 The Chromium 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/services/storage/public/mojom/storage_key/storage_key_mojom_traits.h"
+
+#include "components/services/storage/public/cpp/storage_key.h"
+#include "components/services/storage/public/mojom/storage_key/storage_key.mojom.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace storage {
+namespace {
+
+TEST(StorageKeyMojomTraitsTest, SerializeAndDeserialize) {
+  StorageKey test_keys[] = {
+      StorageKey(url::Origin::Create(GURL("https://example.com"))),
+      StorageKey(url::Origin::Create(GURL("http://example.com"))),
+      StorageKey(url::Origin::Create(GURL("https://example.test"))),
+      StorageKey(url::Origin::Create(GURL("https://sub.example.com"))),
+      StorageKey(url::Origin::Create(GURL("http://sub2.example.com"))),
+      StorageKey(url::Origin()),
+  };
+
+  for (auto& original : test_keys) {
+    StorageKey copied;
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StorageKey>(original,
+                                                                       copied));
+    EXPECT_EQ(original, copied);
+  }
+}
+
+}  // namespace
+}  // namespace storage
\ No newline at end of file
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index 22168e2..2158330 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -165,63 +165,66 @@
   content::RenderFrameHost* frame_host =
       navigation_handle->GetRenderFrameHost();
 
+  base::Optional<blink::FrameAdEvidence> ad_evidence_for_navigation;
+
   // Update the ad status of a frame given the new navigation. This may tag or
   // untag a frame as an ad.
   if (!navigation_handle->IsInMainFrame()) {
     blink::FrameAdEvidence& ad_evidence = EnsureFrameAdEvidence(frame_host);
+    DCHECK_EQ(ad_evidence.parent_is_ad(),
+              base::Contains(ad_frames_,
+                             frame_host->GetParent()->GetFrameTreeNodeId()));
     ad_evidence.set_is_complete();
+    ad_evidence_for_navigation = ad_evidence;
 
     SetIsAdSubframe(frame_host, ad_evidence.IndicatesAdSubframe());
   }
 
+  mojom::ActivationState activation_state =
+      ActivationStateForNextCommittedLoad(navigation_handle);
+
+  TRACE_EVENT2(
+      TRACE_DISABLED_BY_DEFAULT("loading"),
+      "ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation",
+      "activation_state", static_cast<int>(activation_state.activation_level),
+      "render_frame_host", navigation_handle->GetRenderFrameHost());
+
+  mojo::AssociatedRemote<mojom::SubresourceFilterAgent> agent;
+  frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
+
+  // We send `ad_evidence_for_navigation` even if the frame is not tagged as an
+  // ad. This ensures the renderer's copy is up-to-date, including propagating
+  // it on cross-process navigations.
+  agent->ActivateForNextCommittedLoad(activation_state.Clone(),
+                                      ad_evidence_for_navigation);
+}
+
+mojom::ActivationState
+ContentSubresourceFilterThrottleManager::ActivationStateForNextCommittedLoad(
+    content::NavigationHandle* navigation_handle) {
   if (navigation_handle->GetNetErrorCode() != net::OK)
-    return;
+    return mojom::ActivationState();
 
   auto it =
       ongoing_activation_throttles_.find(navigation_handle->GetNavigationId());
   if (it == ongoing_activation_throttles_.end())
-    return;
+    return mojom::ActivationState();
 
   // Main frame throttles with disabled page-level activation will not have
   // associated filters.
   ActivationStateComputingNavigationThrottle* throttle = it->second;
   AsyncDocumentSubresourceFilter* filter = throttle->filter();
   if (!filter)
-    return;
+    return mojom::ActivationState();
 
   // A filter with DISABLED activation indicates a corrupted ruleset.
-  mojom::ActivationLevel level = filter->activation_state().activation_level;
-  if (level == mojom::ActivationLevel::kDisabled)
-    return;
-
-  TRACE_EVENT2(
-      TRACE_DISABLED_BY_DEFAULT("loading"),
-      "ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation",
-      "activation_state", static_cast<int>(level), "render_frame_host",
-      frame_host);
-
-  throttle->WillSendActivationToRenderer();
-
-  bool is_ad_subframe =
-      base::Contains(ad_frames_, navigation_handle->GetFrameTreeNodeId());
-  DCHECK(!is_ad_subframe || !navigation_handle->IsInMainFrame());
-
-  base::Optional<blink::FrameAdEvidence> ad_evidence;
-  if (!navigation_handle->IsInMainFrame()) {
-    ad_evidence = EnsureFrameAdEvidence(frame_host);
-    DCHECK_EQ(ad_evidence->IndicatesAdSubframe(), is_ad_subframe);
-    DCHECK_EQ(ad_evidence->parent_is_ad(),
-              base::Contains(ad_frames_,
-                             frame_host->GetParent()->GetFrameTreeNodeId()));
+  if (filter->activation_state().activation_level ==
+      mojom::ActivationLevel::kDisabled) {
+    return mojom::ActivationState();
   }
 
-  mojo::AssociatedRemote<mojom::SubresourceFilterAgent> agent;
-  frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
-
-  // TODO(crbug.com/1190189): Notify the renderer even when activation is
-  // disabled.
-  agent->ActivateForNextCommittedLoad(filter->activation_state().Clone(),
-                                      ad_evidence);
+  throttle->WillSendActivationToRenderer();
+  return filter->activation_state();
 }
 
 void ContentSubresourceFilterThrottleManager::DidFinishNavigation(
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
index 64707b3..514b5aeb 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
@@ -226,6 +226,9 @@
   blink::FrameAdEvidence& EnsureFrameAdEvidence(
       content::RenderFrameHost* render_frame_host);
 
+  mojom::ActivationState ActivationStateForNextCommittedLoad(
+      content::NavigationHandle* navigation_handle);
+
   // Registers `render_frame_host` as an ad frame. If the frame later moves to
   // a new process its RenderHost will be told that it's an ad.
   void OnFrameIsAdSubframe(content::RenderFrameHost* render_frame_host);
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
index 7c41686..34b1118 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
@@ -98,9 +98,11 @@
     is_ad_subframe_ = false;
     return is_ad_subframe;
   }
-  bool LastActivated() {
-    bool activated = last_activation_ && last_activation_->activation_level !=
-                                             mojom::ActivationLevel::kDisabled;
+  base::Optional<bool> LastActivated() {
+    if (!last_activation_)
+      return base::nullopt;
+    bool activated =
+        last_activation_->activation_level != mojom::ActivationLevel::kDisabled;
     last_activation_.reset();
     return activated;
   }
@@ -227,16 +229,20 @@
     content::RenderViewHostTestHarness::TearDown();
   }
 
-  void ExpectActivationSignalForFrame(content::RenderFrameHost* rfh,
-                                      bool expect_activation,
-                                      bool expect_is_ad_subframe = false) {
+  void ExpectActivationSignalForFrame(
+      content::RenderFrameHost* rfh,
+      bool expect_activation,
+      bool expect_is_ad_subframe = false,
+      bool expect_activation_sent_to_agent = true) {
     // In some cases we need to verify that messages were _not_ sent, in which
     // case using a Wait() idiom would cause hangs. RunUntilIdle instead to
     // ensure mojo calls make it to the fake agent.
     base::RunLoop().RunUntilIdle();
     FakeSubresourceFilterAgent* agent = agent_map_[rfh].get();
-    EXPECT_EQ(expect_activation, agent->LastActivated());
+    base::Optional<bool> last_activated = agent->LastActivated();
+    EXPECT_EQ(expect_activation, last_activated && *last_activated);
     EXPECT_EQ(expect_is_ad_subframe, agent->LastAdSubframe());
+    EXPECT_EQ(expect_activation_sent_to_agent, last_activated.has_value());
   }
 
   // Helper methods:
@@ -615,7 +621,12 @@
   GURL url2 = GURL(base::StringPrintf("%s#ref", kTestURLWithActivation));
   CreateTestNavigation(url2, main_rfh());
   navigation_simulator()->CommitSameDocument();
-  ExpectActivationSignalForFrame(main_rfh(), false /* expect_activation */);
+
+  // Same-document navigations do not pass through ReadyToCommitNavigation so no
+  // ActivateForNextCommittedLoad mojo call is expected.
+  ExpectActivationSignalForFrame(main_rfh(), false /* expect_activation */,
+                                 false /* expect_is_ad_subframe */,
+                                 false /* expect_activation_sent_to_agent */);
 
   EXPECT_TRUE(ads_blocked_in_content_settings());
 #if defined(OS_ANDROID)
@@ -746,7 +757,12 @@
   CreateTestNavigation(same_site_inactive_url, main_rfh());
   SimulateFailedNavigation(navigation_simulator(), net::ERR_ABORTED);
   EXPECT_TRUE(ManagerHasRulesetHandle());
-  ExpectActivationSignalForFrame(main_rfh(), false /* expect_activation */);
+
+  // The aborted navigation does not pass through ReadyToCommitNavigation so no
+  // ActivateForNextCommittedLoad mojo call is expected.
+  ExpectActivationSignalForFrame(main_rfh(), false /* expect_activation */,
+                                 false /* expect_is_ad_subframe */,
+                                 false /* expect_activation_sent_to_agent */);
 
   // A subframe navigation fail.
   CreateSubframeWithTestNavigation(
diff --git a/components/translate/core/browser/translate_infobar_delegate.cc b/components/translate/core/browser/translate_infobar_delegate.cc
index b03f2f88..dc8fcc4 100644
--- a/components/translate/core/browser/translate_infobar_delegate.cc
+++ b/components/translate/core/browser/translate_infobar_delegate.cc
@@ -330,6 +330,11 @@
     return false;
   }
 
+  // Don't trigger for unknown source language.
+  if (source_language_code() == translate::kUnknownLanguageCode) {
+    return false;
+  }
+
   bool always_translate =
       (GetTranslationAcceptedCount() >= GetAutoAlwaysThreshold() &&
        GetTranslationAutoAlwaysCount() < GetMaximumNumberOfAutoAlways());
diff --git a/components/translate/core/browser/translate_infobar_delegate_unittest.cc b/components/translate/core/browser/translate_infobar_delegate_unittest.cc
index 9a47dd8b..2565f28 100644
--- a/components/translate/core/browser/translate_infobar_delegate_unittest.cc
+++ b/components/translate/core/browser/translate_infobar_delegate_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_pref_names.h"
 #include "components/translate/core/browser/translate_prefs.h"
+#include "components/translate/core/common/translate_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
@@ -40,6 +41,8 @@
 
 namespace {
 
+const int kAutoAlwaysThreshold = 5;
+
 class TestInfoBarManager : public infobars::InfoBarManager {
  public:
   TestInfoBarManager() = default;
@@ -216,14 +219,14 @@
       pref_service_.get(), TranslatePrefs::kPrefTranslateAcceptedCount);
   base::DictionaryValue* update_translate_accepted_dict =
       update_translate_accepted_count.Get();
-  // 6 = kAutoAlwaysThreshold + 1
-  update_translate_accepted_dict->SetInteger(kSourceLanguage, 6);
+  update_translate_accepted_dict->SetInteger(kSourceLanguage,
+                                             kAutoAlwaysThreshold + 1);
 
   const base::DictionaryValue* dict = pref_service_->GetDictionary(
       TranslatePrefs::kPrefTranslateAutoAlwaysCount);
-  int translate_auto_always_count = 0;
+  int translate_auto_always_count = -1;
   dict->GetInteger(kSourceLanguage, &translate_auto_always_count);
-  EXPECT_EQ(0, translate_auto_always_count);
+  EXPECT_EQ(-1, translate_auto_always_count);
 
   TranslateInfoBarDelegate::Create(
       /*replace_existing_infobar=*/true, manager_->GetWeakPtr(),
@@ -239,13 +242,50 @@
   int count = -1;
   update_translate_accepted_dict->GetInteger(kSourceLanguage, &count);
   EXPECT_EQ(0, count);
+  // Get the dictionary again in order to update it.
   dict = pref_service_->GetDictionary(
       TranslatePrefs::kPrefTranslateAutoAlwaysCount);
-  translate_auto_always_count = 0;
   dict->GetInteger(kSourceLanguage, &translate_auto_always_count);
   EXPECT_EQ(1, translate_auto_always_count);
 }
 
+TEST_F(TranslateInfoBarDelegateTest, ShouldNotAutoAlwaysTranslateUnknown) {
+  DictionaryPrefUpdate update_translate_accepted_count(
+      pref_service_.get(), TranslatePrefs::kPrefTranslateAcceptedCount);
+  base::DictionaryValue* update_translate_accepted_dict =
+      update_translate_accepted_count.Get();
+  // Should not trigger auto always translate for unknown source language.
+  update_translate_accepted_dict->SetInteger(kUnknownLanguageCode,
+                                             kAutoAlwaysThreshold + 1);
+
+  const base::DictionaryValue* dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoAlwaysCount);
+  int translate_auto_always_count = -1;
+  dict->GetInteger(kUnknownLanguageCode, &translate_auto_always_count);
+  EXPECT_EQ(-1, translate_auto_always_count);
+
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/true, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/false,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING,
+      kUnknownLanguageCode, kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_FALSE(delegate->ShouldAutoAlwaysTranslate());
+
+  int count = -1;
+  update_translate_accepted_dict->GetInteger(kSourceLanguage, &count);
+  // Always translate not triggered, so count should be unchanged.
+  EXPECT_EQ(-1, count);
+  // Get the dictionary again in order to update it.
+  dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoAlwaysCount);
+  dict->GetInteger(kUnknownLanguageCode, &translate_auto_always_count);
+  EXPECT_EQ(-1, translate_auto_always_count);
+}
+
 TEST_F(TranslateInfoBarDelegateTest, ShouldNotAutoAlwaysTranslate) {
   // Create an off record info bar.
   TranslateInfoBarDelegate::Create(
@@ -294,6 +334,7 @@
   int count = -1;
   update_translate_denied_dict->GetInteger(kSourceLanguage, &count);
   EXPECT_EQ(0, count);
+  // Get the dictionary again in order to update it.
   dict = pref_service_->GetDictionary(
       TranslatePrefs::kPrefTranslateAutoNeverCount);
   translate_auto_never_count = 0;
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.cc b/components/translate/core/browser/translate_metrics_logger_impl.cc
index 615bc713..55cfb58b 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl.cc
@@ -60,10 +60,6 @@
     "Translate.PageLoad.Ranker.Version";
 const char kTranslatePageLoadTriggerDecision[] =
     "Translate.PageLoad.TriggerDecision";
-const char kTranslatePageLoadTriggerDecisionAllTriggerDecisions[] =
-    "Translate.PageLoad.TriggerDecision.AllTriggerDecisions";
-const char kTranslatePageLoadTriggerDecisionTotalCount[] =
-    "Translate.PageLoad.TriggerDecision.TotalCount";
 
 TranslationType NullTranslateMetricsLogger::GetNextManualTranslationType() {
   return TranslationType::kUninitialized;
@@ -189,12 +185,6 @@
 
   base::UmaHistogramEnumeration(kTranslatePageLoadTriggerDecision,
                                 trigger_decision_);
-  base::UmaHistogramCounts100(kTranslatePageLoadTriggerDecisionTotalCount,
-                              all_trigger_decisions_.size());
-  for (const auto& trigger_decision : all_trigger_decisions_) {
-    base::UmaHistogramEnumeration(
-        kTranslatePageLoadTriggerDecisionAllTriggerDecisions, trigger_decision);
-  }
   if (has_href_translate_target_) {
     base::UmaHistogramEnumeration(kTranslatePageLoadHrefTriggerDecision,
                                   trigger_decision_);
@@ -283,8 +273,6 @@
        trigger_decision_ != TriggerDecision::kAutomaticTranslationByHref)) {
     trigger_decision_ = trigger_decision;
   }
-
-  all_trigger_decisions_.push_back(trigger_decision);
 }
 
 void TranslateMetricsLoggerImpl::LogAutofillAssistantDeferredTriggerDecision() {
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.h b/components/translate/core/browser/translate_metrics_logger_impl.h
index c4f57f1c..f7d363ed 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.h
+++ b/components/translate/core/browser/translate_metrics_logger_impl.h
@@ -46,8 +46,6 @@
 extern const char kTranslatePageLoadRankerTimerShouldOfferTranslation[];
 extern const char kTranslatePageLoadRankerVersion[];
 extern const char kTranslatePageLoadTriggerDecision[];
-extern const char kTranslatePageLoadTriggerDecisionAllTriggerDecisions[];
-extern const char kTranslatePageLoadTriggerDecisionTotalCount[];
 
 class NullTranslateMetricsLogger : public TranslateMetricsLogger {
  public:
@@ -210,7 +208,6 @@
   // Stores the reason for the initial state of the page load. In the case there
   // are multiple reasons, only the first reported reason is stored.
   TriggerDecision trigger_decision_ = TriggerDecision::kUninitialized;
-  std::vector<TriggerDecision> all_trigger_decisions_;
   bool autofill_assistant_deferred_trigger_decision_ = false;
 
   // Tracks the different dimensions that determine the state of Translate.
diff --git a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
index 88662360..a5f0ff1a 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
@@ -599,35 +599,6 @@
   // Make sure that the href trigger decision wasn't logged.
   histogram_tester()->ExpectTotalCount(kTranslatePageLoadHrefTriggerDecision,
                                        0);
-
-  // Check that the expected values are recorded to the
-  // Translate.PageLoad.TriggerDecision.TotalCount and
-  // Translate.PageLoad.TriggerDecision.AllTriggerDecisions histograms.
-  histogram_tester()->ExpectUniqueSample(
-      kTranslatePageLoadTriggerDecisionTotalCount, 9, 1);
-  histogram_tester()->ExpectTotalCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions, 9);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kAutomaticTranslationByLink, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledByRanker, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledUnsupportedLanguage, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledOffline, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledTranslationFeatureDisabled, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kShowUI, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledNeverTranslateLanguage, 1);
 }
 
 TEST_F(TranslateMetricsLoggerImplTest, LogHrefTriggerDecision) {
@@ -657,35 +628,6 @@
   // Make sure that the href trigger decision was logged.
   histogram_tester()->ExpectUniqueSample(kTranslatePageLoadHrefTriggerDecision,
                                          kTriggerDecisions[0], 1);
-
-  // Check that the expected values are recorded to the
-  // Translate.PageLoad.TriggerDecision.TotalCount and
-  // Translate.PageLoad.TriggerDecision.AllTriggerDecisions histograms.
-  histogram_tester()->ExpectUniqueSample(
-      kTranslatePageLoadTriggerDecisionTotalCount, 9, 1);
-  histogram_tester()->ExpectTotalCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions, 9);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kAutomaticTranslationByLink, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledByRanker, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledUnsupportedLanguage, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledOffline, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledTranslationFeatureDisabled, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kShowUI, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslatePageLoadTriggerDecisionAllTriggerDecisions,
-      TriggerDecision::kDisabledNeverTranslateLanguage, 1);
 }
 
 TEST_F(TranslateMetricsLoggerImplTest, LogHrefOverrideTriggerDecision) {
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index ee326a1..ef821cd 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -48,11 +48,11 @@
 }
 
 bool IsWhitelistedSourceId(SourceId source_id) {
-  return GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID ||
-         GetSourceIdType(source_id) == SourceIdType::APP_ID ||
-         GetSourceIdType(source_id) == SourceIdType::HISTORY_ID ||
-         GetSourceIdType(source_id) == SourceIdType::WEBAPK_ID ||
-         GetSourceIdType(source_id) == SourceIdType::PAYMENT_APP_ID;
+  SourceIdType type = GetSourceIdType(source_id);
+  return type == SourceIdType::NAVIGATION_ID || type == SourceIdType::APP_ID ||
+         type == SourceIdType::HISTORY_ID || type == SourceIdType::WEBAPK_ID ||
+         type == SourceIdType::PAYMENT_APP_ID ||
+         type == SourceIdType::NO_URL_ID;
 }
 
 // Returns whether |url| has one of the schemes supported for logging to UKM.
@@ -337,10 +337,12 @@
   for (const auto& kv : recordings_.sources) {
     // Don't keep sources of these types after current report because their
     // entries are logged only at source creation time.
-    if (GetSourceIdType(kv.first) == ukm::SourceIdObj::Type::APP_ID ||
-        GetSourceIdType(kv.first) == ukm::SourceIdObj::Type::HISTORY_ID ||
-        GetSourceIdType(kv.first) == ukm::SourceIdObj::Type::WEBAPK_ID ||
-        GetSourceIdType(kv.first) == SourceIdType::PAYMENT_APP_ID) {
+    SourceIdType type = GetSourceIdType(kv.first);
+    if (type == ukm::SourceIdObj::Type::APP_ID ||
+        type == ukm::SourceIdObj::Type::HISTORY_ID ||
+        type == ukm::SourceIdObj::Type::WEBAPK_ID ||
+        type == SourceIdType::PAYMENT_APP_ID ||
+        type == SourceIdType::NO_URL_ID) {
       MarkSourceForDeletion(kv.first);
     }
     // If the source id is not whitelisted, don't send it unless it has
@@ -579,6 +581,7 @@
 void UkmRecorderImpl::UpdateSourceURL(SourceId source_id,
                                       const GURL& unsanitized_url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(GetSourceIdType(source_id) != SourceIdType::NO_URL_ID);
 
   if (base::Contains(recordings_.sources, source_id))
     return;
diff --git a/components/viz/service/display_embedder/image_context_impl.cc b/components/viz/service/display_embedder/image_context_impl.cc
index 5002a214..bcaa036 100644
--- a/components/viz/service/display_embedder/image_context_impl.cc
+++ b/components/viz/service/display_embedder/image_context_impl.cc
@@ -139,7 +139,8 @@
   if (BindOrCopyTextureIfNecessary(texture_base, &texture_size) &&
       texture_size != size()) {
     DLOG(ERROR) << "Failed to fulfill the promise texture - texture "
-                   "size does not match TransferableResource size.";
+                   "size does not match TransferableResource size: "
+                << texture_size.ToString() << " vs " << size().ToString();
     CreateFallbackImage(context_state);
     return;
   }
@@ -202,7 +203,9 @@
 
     if (representation->size() != size()) {
       DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
-                     "size does not match TransferableResource size.";
+                     "size does not match TransferableResource size: "
+                  << representation->size().ToString() << " vs "
+                  << size().ToString();
       return false;
     }
 
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index 6fb5bd7b..f0b8ae69 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -526,6 +526,40 @@
   }
 }
 
+void BrowserAccessibilityManagerAndroid::OnNodeCreated(ui::AXTree* tree,
+                                                       ui::AXNode* node) {
+  BrowserAccessibilityManager::OnNodeCreated(tree, node);
+  if (node->data().GetBoolAttribute(
+          ax::mojom::BoolAttribute::kTouchPassthrough)) {
+    auto* root =
+        static_cast<BrowserAccessibilityManagerAndroid*>(GetRootManager());
+    if (root)
+      root->EnableTouchPassthrough();
+    else
+      EnableTouchPassthrough();
+  }
+}
+
+void BrowserAccessibilityManagerAndroid::OnBoolAttributeChanged(
+    ui::AXTree* tree,
+    ui::AXNode* node,
+    ax::mojom::BoolAttribute attr,
+    bool new_value) {
+  BrowserAccessibilityManager::OnBoolAttributeChanged(tree, node, attr,
+                                                      new_value);
+  if (new_value && attr == ax::mojom::BoolAttribute::kTouchPassthrough) {
+    // TODO(accessibility): there's a tiny chance we could get this
+    // called on an iframe before it's attached to the root manager.
+    // If this ever becomes an issue in practice, make this more robust.
+    auto* root =
+        static_cast<BrowserAccessibilityManagerAndroid*>(GetRootManager());
+    if (root)
+      root->EnableTouchPassthrough();
+    else
+      EnableTouchPassthrough();
+  }
+}
+
 WebContentsAccessibilityAndroid*
 BrowserAccessibilityManagerAndroid::GetWebContentsAXFromRootManager() {
   BrowserAccessibility* parent_node = GetParentNodeFromParentTree();
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h
index 90af273..a47a3b4 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.h
+++ b/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -114,14 +114,28 @@
   // Helper method to clear AccessibilityNodeInfo cache on given node
   void ClearNodeInfoCacheForGivenId(int32_t unique_id);
 
+  // Only set on the root BrowserAccessibilityManager. Keeps track of if
+  // any node uses touch passthrough in any frame - if so, any incoming
+  // touch event needs to be processed for possible forwarding. This is
+  // just an optimization; once touch passthrough is enabled it stays
+  // on for this main frame until the page is reloaded. In the future if
+  // there's a need to optimize for touch passthrough being enabled only
+  // temporarily, this would need to be more sophisticated.
+  void EnableTouchPassthrough() { touch_passthrough_enabled_ = true; }
+  bool touch_passthrough_enabled() const { return touch_passthrough_enabled_; }
+
  private:
   // AXTreeObserver overrides.
   void OnAtomicUpdateFinished(
       ui::AXTree* tree,
       bool root_changed,
       const std::vector<ui::AXTreeObserver::Change>& changes) override;
-
   void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
+  void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override;
+  void OnBoolAttributeChanged(ui::AXTree* tree,
+                              ui::AXNode* node,
+                              ax::mojom::BoolAttribute attr,
+                              bool new_value) override;
 
   WebContentsAccessibilityAndroid* GetWebContentsAXFromRootManager();
 
@@ -143,6 +157,11 @@
   // Whether this manager is running as part of a WebView.
   bool is_running_as_webview_ = false;
 
+  // Only set on the root BrowserAccessibilityManager. Keeps track of if
+  // any node uses touch passthrough in any frame. See comment next to
+  // any_node_uses_touch_passthrough(), above, for details.
+  bool touch_passthrough_enabled_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerAndroid);
 };
 
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc
index 01ad946..b7bf1fd 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.cc
+++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -22,6 +22,7 @@
 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
 #include "content/browser/accessibility/browser_accessibility_state_impl_android.h"
 #include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
+#include "content/browser/accessibility/touch_passthrough_manager.h"
 #include "content/browser/android/render_widget_host_connector.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -484,16 +485,48 @@
           ui::MotionEventAndroid::GetAndroidAction(event.GetAction())))
     return false;
 
+  if (!GetRootBrowserAccessibilityManager())
+    return true;
+
+  // Apply the page scale factor to go from device coordinates to
+  // render coordinates.
+  gfx::PointF pointf = event.GetPointPix();
+  pointf.Scale(1 / page_scale_);
+  gfx::Point point = gfx::ToFlooredPoint(pointf);
+
   // |HitTest| sends an IPC to the render process to do the hit testing.
   // The response is handled by HandleHover when it returns.
   // Hover event was consumed by accessibility by now. Return true to
   // stop the event from proceeding.
-  if (event.GetAction() != ui::MotionEvent::Action::HOVER_EXIT &&
-      GetRootBrowserAccessibilityManager()) {
-    gfx::PointF point = event.GetPointPix();
-    point.Scale(1 / page_scale_);
-    GetRootBrowserAccessibilityManager()->HitTest(gfx::ToFlooredPoint(point));
+  if (event.GetAction() != ui::MotionEvent::Action::HOVER_EXIT)
+    GetRootBrowserAccessibilityManager()->HitTest(point);
+
+  if (!GetRootBrowserAccessibilityManager()->touch_passthrough_enabled())
+    return true;
+
+  if (!web_contents_ || !web_contents_->GetMainFrame())
+    return true;
+
+  if (!touch_passthrough_manager_) {
+    touch_passthrough_manager_ = std::make_unique<TouchPassthroughManager>(
+        web_contents_->GetMainFrame());
   }
+
+  switch (event.GetAction()) {
+    case ui::MotionEvent::Action::HOVER_ENTER:
+      touch_passthrough_manager_->OnTouchStart(point);
+      break;
+    case ui::MotionEvent::Action::HOVER_MOVE:
+      touch_passthrough_manager_->OnTouchMove(point);
+      break;
+    case ui::MotionEvent::Action::HOVER_EXIT:
+      touch_passthrough_manager_->OnTouchEnd();
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+
   return true;
 }
 
diff --git a/content/browser/accessibility/web_contents_accessibility_android.h b/content/browser/accessibility/web_contents_accessibility_android.h
index f82185a..4cf809b 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.h
+++ b/content/browser/accessibility/web_contents_accessibility_android.h
@@ -33,6 +33,7 @@
 
 class BrowserAccessibilityAndroid;
 class BrowserAccessibilityManagerAndroid;
+class TouchPassthroughManager;
 class WebContents;
 class WebContentsImpl;
 
@@ -373,6 +374,8 @@
   // this class is constructed with a ui::AXTreeUpdate.
   std::unique_ptr<BrowserAccessibilityManagerAndroid> manager_;
 
+  std::unique_ptr<TouchPassthroughManager> touch_passthrough_manager_;
+
   base::WeakPtrFactory<WebContentsAccessibilityAndroid> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(WebContentsAccessibilityAndroid);
diff --git a/content/browser/conversions/conversion_internals.mojom b/content/browser/conversions/conversion_internals.mojom
index 4103615f..f88bf480 100644
--- a/content/browser/conversions/conversion_internals.mojom
+++ b/content/browser/conversions/conversion_internals.mojom
@@ -20,7 +20,6 @@
   url.mojom.Origin conversion_origin;
   url.mojom.Origin reporting_origin;
   double report_time;
-  int32 attribution_credit;
   SourceType source_type;
 };
 
diff --git a/content/browser/conversions/conversion_internals_browsertest.cc b/content/browser/conversions/conversion_internals_browsertest.cc
index 8429931..5d5e487 100644
--- a/content/browser/conversions/conversion_internals_browsertest.cc
+++ b/content/browser/conversions/conversion_internals_browsertest.cc
@@ -289,8 +289,8 @@
     let obs = new MutationObserver(() => {
       if (table.children.length === 2 &&
           table.children[0].children[1].innerText === "7" &&
-          table.children[0].children[6].innerText === "Navigation" &&
-          table.children[1].children[6].innerText === "Event") {
+          table.children[0].children[5].innerText === "Navigation" &&
+          table.children[1].children[5].innerText === "Event") {
         document.title = $1;
       }
     });
diff --git a/content/browser/conversions/conversion_internals_handler_impl.cc b/content/browser/conversions/conversion_internals_handler_impl.cc
index 07a2c28..76e6a8c 100644
--- a/content/browser/conversions/conversion_internals_handler_impl.cc
+++ b/content/browser/conversions/conversion_internals_handler_impl.cc
@@ -66,7 +66,6 @@
         report.impression.impression_data(), report.conversion_data,
         report.impression.conversion_origin(),
         report.impression.reporting_origin(), report.report_time.ToJsTime(),
-        report.attribution_credit,
         SourceTypeToMojoType(report.impression.source_type())));
   }
   std::move(web_ui_callback).Run(std::move(web_ui_reports));
diff --git a/content/browser/conversions/conversion_manager_impl_unittest.cc b/content/browser/conversions/conversion_manager_impl_unittest.cc
index 42b3e49..94df30a 100644
--- a/content/browser/conversions/conversion_manager_impl_unittest.cc
+++ b/content/browser/conversions/conversion_manager_impl_unittest.cc
@@ -214,7 +214,6 @@
       /*conversion_time=*/clock().Now(),
       /*report_time=*/clock().Now() + kFirstReportingWindow,
       base::nullopt /* conversion_id */);
-  expected_report.attribution_credit = 100;
 
   base::RunLoop run_loop;
   auto reports_callback =
@@ -335,14 +334,12 @@
 TEST_F(ConversionManagerImplTest, ConversionsSentFromUI_ReportedImmediately) {
   conversion_manager_->HandleImpression(
       ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
-  conversion_manager_->HandleImpression(
-      ImpressionBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
   conversion_manager_->HandleConversion(DefaultConversion());
   EXPECT_EQ(0u, test_reporter_->num_reports());
 
   conversion_manager_->SendReportsForWebUI(base::DoNothing());
   task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(0));
-  EXPECT_EQ(2u, test_reporter_->num_reports());
+  EXPECT_EQ(1u, test_reporter_->num_reports());
 }
 
 // TODO(crbug.com/1088449): Flaky on Linux and Android.
diff --git a/content/browser/conversions/conversion_network_sender_impl.cc b/content/browser/conversions/conversion_network_sender_impl.cc
index 50b8620..c448239 100644
--- a/content/browser/conversions/conversion_network_sender_impl.cc
+++ b/content/browser/conversions/conversion_network_sender_impl.cc
@@ -74,10 +74,9 @@
   url::Replacements<char> replacements;
   const char kEndpointPath[] = "/.well-known/register-conversion";
   replacements.SetPath(kEndpointPath, url::Component(0, strlen(kEndpointPath)));
-  std::string query = base::StrCat(
-      {"impression-data=", report.impression.impression_data(),
-       "&conversion-data=", report.conversion_data,
-       "&credit=", base::NumberToString(report.attribution_credit)});
+  std::string query =
+      base::StrCat({"impression-data=", report.impression.impression_data(),
+                    "&conversion-data=", report.conversion_data});
   replacements.SetQuery(query.c_str(), url::Component(0, query.length()));
   return report.impression.reporting_origin().GetURL().ReplaceComponents(
       replacements);
diff --git a/content/browser/conversions/conversion_network_sender_impl_unittest.cc b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
index 1275ebc..4f7a937 100644
--- a/content/browser/conversions/conversion_network_sender_impl_unittest.cc
+++ b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
@@ -36,7 +36,7 @@
 std::string GetReportUrl(std::string impression_data) {
   return base::StrCat(
       {"https://report.test/.well-known/register-conversion?impression-data=",
-       impression_data, "&conversion-data=", impression_data, "&credit=0"});
+       impression_data, "&conversion-data=", impression_data});
 }
 
 // Create a simple report where impression data/conversion data/conversion id
@@ -198,13 +198,12 @@
                           /*conversion_time=*/base::Time(),
                           /*report_time=*/base::Time(),
                           /*conversion_id=*/1);
-  report.attribution_credit = 50;
   network_sender_->SendReport(&report, base::DoNothing());
 
   std::string expected_report_url(
       "https://a.com/.well-known/"
       "register-conversion?impression-data=impression&conversion-data="
-      "conversion&credit=50");
+      "conversion");
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       expected_report_url, ""));
 }
@@ -226,7 +225,7 @@
   const network::ResourceRequest* pending_request;
   std::string expected_report_url(
       "https://a.com/.well-known/"
-      "register-conversion?impression-data=1&conversion-data=1&credit=0");
+      "register-conversion?impression-data=1&conversion-data=1");
   EXPECT_TRUE(test_url_loader_factory_.IsPending(expected_report_url,
                                                  &pending_request));
 
diff --git a/content/browser/conversions/conversion_report.cc b/content/browser/conversions/conversion_report.cc
index 2dc00f8b..f6c233f 100644
--- a/content/browser/conversions/conversion_report.cc
+++ b/content/browser/conversions/conversion_report.cc
@@ -31,8 +31,7 @@
       << ", conversion_data: " << report.conversion_data
       << ", conversion_time: " << report.conversion_time
       << ", report_time: " << report.report_time
-      << ", extra_delay: " << report.extra_delay
-      << ", attribution_credit: " << report.attribution_credit;
+      << ", extra_delay: " << report.extra_delay;
   return out;
 }
 
diff --git a/content/browser/conversions/conversion_report.h b/content/browser/conversions/conversion_report.h
index 95a274f7..644e187d 100644
--- a/content/browser/conversions/conversion_report.h
+++ b/content/browser/conversions/conversion_report.h
@@ -46,12 +46,6 @@
   // the purposes of logging metrics.
   base::TimeDelta extra_delay;
 
-  // The attribution credit assigned to this conversion report. This is derived
-  // from the set of all impressions that matched a singular conversion event.
-  // This should be in the range 0-100. A set of ConversionReports for one
-  // conversion event should have their |attribution_credit| sum equal to 100.
-  int attribution_credit = 0;
-
   // Id assigned by storage to uniquely identify a completed conversion. If
   // null, an ID has not been assigned yet.
   const base::Optional<int64_t> conversion_id;
diff --git a/content/browser/conversions/conversion_storage.h b/content/browser/conversions/conversion_storage.h
index 2de6dd2..338a540 100644
--- a/content/browser/conversions/conversion_storage.h
+++ b/content/browser/conversions/conversion_storage.h
@@ -29,15 +29,19 @@
    public:
     virtual ~Delegate() = default;
 
-    // New conversions will be sent through this callback for
+    // Returns the impression to attribute for a particular conversion.
+    // |impressions| is the list of all impressions which matched the
+    // conversion, and is guaranteed to be non-empty.
+    virtual const StorableImpression& GetImpressionToAttribute(
+        const std::vector<StorableImpression>& impressions) = 0;
+
+    // New conversion reports will be sent through this callback for
     // pruning/modification before they are added to storage. This will be
     // called during the execution of
-    // ConversionStorage::MaybeCreateAndStoreConversionReports(). |reports| will
-    // contain a report for each matching impression for a given conversion
-    // event. Each report will be pre-populated from storage with the conversion
+    // ConversionStorage::MaybeCreateAndStoreConversionReports().
+    // The report will be pre-populated from storage with the conversion
     // event data.
-    virtual void ProcessNewConversionReports(
-        std::vector<ConversionReport>* reports) = 0;
+    virtual void ProcessNewConversionReport(ConversionReport& report) = 0;
 
     // This limit is used to determine if an impression is allowed to schedule
     // a new conversion reports. When an impression reaches this limit it is
diff --git a/content/browser/conversions/conversion_storage_delegate_impl.cc b/content/browser/conversions/conversion_storage_delegate_impl.cc
index 1f09f353..0ba10279 100644
--- a/content/browser/conversions/conversion_storage_delegate_impl.cc
+++ b/content/browser/conversions/conversion_storage_delegate_impl.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/conversions/conversion_storage_delegate_impl.h"
 
+#include <algorithm>
+
 namespace content {
 
 ConversionStorageDelegateImpl::ConversionStorageDelegateImpl(bool debug_mode)
@@ -11,25 +13,23 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-void ConversionStorageDelegateImpl::ProcessNewConversionReports(
-    std::vector<ConversionReport>* reports) {
+const StorableImpression&
+ConversionStorageDelegateImpl::GetImpressionToAttribute(
+    const std::vector<StorableImpression>& impressions) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!reports->empty());
-  ConversionReport* last_report = &(reports->at(0));
+  DCHECK(!impressions.empty());
 
-  // Assign attribution credits to each report that will be sent at report time.
-  // This performs "last click" attribution which assigns the report
-  // for the most recent impression a credit of 100, and the rest a credit of 0.
-  for (ConversionReport& report : *reports) {
-    report.report_time = GetReportTimeForConversion(report);
+  return *std::max_element(
+      impressions.begin(), impressions.end(),
+      [](const StorableImpression& a, const StorableImpression& b) {
+        return a.impression_time() < b.impression_time();
+      });
+}
 
-    report.attribution_credit = 0;
-    if (report.impression.impression_time() >
-        last_report->impression.impression_time())
-      last_report = &report;
-  }
-
-  last_report->attribution_credit = 100;
+void ConversionStorageDelegateImpl::ProcessNewConversionReport(
+    ConversionReport& report) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  report.report_time = GetReportTimeForConversion(report);
 }
 
 int ConversionStorageDelegateImpl::GetMaxConversionsPerImpression(
diff --git a/content/browser/conversions/conversion_storage_delegate_impl.h b/content/browser/conversions/conversion_storage_delegate_impl.h
index 1b04c79..c970200e 100644
--- a/content/browser/conversions/conversion_storage_delegate_impl.h
+++ b/content/browser/conversions/conversion_storage_delegate_impl.h
@@ -9,12 +9,13 @@
 #include "base/time/time.h"
 #include "content/browser/conversions/conversion_report.h"
 #include "content/browser/conversions/conversion_storage.h"
+#include "content/browser/conversions/storable_impression.h"
 #include "content/common/content_export.h"
 
 namespace content {
 
 // Implementation of the storage delegate. This class handles assigning
-// attribution credits and report times to newly created conversion reports. It
+// report times to newly created conversion reports. It
 // also controls constants for ConversionStorage. This is owned by
 // ConversionStorageSql, and should only be accessed on the conversions storage
 // task runner.
@@ -29,8 +30,9 @@
   ~ConversionStorageDelegateImpl() override = default;
 
   // ConversionStorageDelegate:
-  void ProcessNewConversionReports(
-      std::vector<ConversionReport>* reports) override;
+  const StorableImpression& GetImpressionToAttribute(
+      const std::vector<StorableImpression>& impressions) override;
+  void ProcessNewConversionReport(ConversionReport& report) override;
   int GetMaxConversionsPerImpression(
       StorableImpression::SourceType source_type) const override;
   int GetMaxImpressionsPerOrigin() const override;
diff --git a/content/browser/conversions/conversion_storage_delegate_impl_unittest.cc b/content/browser/conversions/conversion_storage_delegate_impl_unittest.cc
index d096c584..4be5c87 100644
--- a/content/browser/conversions/conversion_storage_delegate_impl_unittest.cc
+++ b/content/browser/conversions/conversion_storage_delegate_impl_unittest.cc
@@ -43,11 +43,10 @@
 
 TEST_F(ConversionStorageDelegateImplTest, ImmediateConversion_FirstWindowUsed) {
   base::Time impression_time = base::Time::Now();
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, /*conversion_time=*/impression_time)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2),
-            reports[0].report_time);
+  ConversionReport report =
+      GetReport(impression_time, /*conversion_time=*/impression_time);
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
+  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2), report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
@@ -55,11 +54,9 @@
   base::Time impression_time = base::Time::Now();
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(2) -
                                base::TimeDelta::FromMinutes(1);
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, conversion_time)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(7),
-            reports[0].report_time);
+  ConversionReport report = GetReport(impression_time, conversion_time);
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
+  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(7), report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
@@ -70,11 +67,9 @@
   // before the deadline.
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(2) -
                                base::TimeDelta::FromMinutes(61);
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, conversion_time)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2),
-            reports[0].report_time);
+  ConversionReport report = GetReport(impression_time, conversion_time);
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
+  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2), report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
@@ -83,12 +78,10 @@
   base::Time conversion_time = impression_time + base::TimeDelta::FromHours(1);
 
   // Set the impression to expire before the two day window.
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, conversion_time,
-                /*expiry=*/base::TimeDelta::FromHours(2))};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2),
-            reports[0].report_time);
+  ConversionReport report = GetReport(impression_time, conversion_time,
+                                      /*expiry=*/base::TimeDelta::FromHours(2));
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
+  EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2), report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
@@ -97,15 +90,14 @@
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(3);
 
   // Set the impression to expire before the two day window.
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, conversion_time,
-                /*expiry=*/base::TimeDelta::FromDays(4))};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
+  ConversionReport report = GetReport(impression_time, conversion_time,
+                                      /*expiry=*/base::TimeDelta::FromDays(4));
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
 
   // The expiry window is reported one hour after expiry time.
   EXPECT_EQ(impression_time + base::TimeDelta::FromDays(4) +
                 base::TimeDelta::FromHours(1),
-            reports[0].report_time);
+            report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
@@ -114,68 +106,40 @@
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(7);
 
   // Set the impression to expire before the two day window.
-  std::vector<ConversionReport> reports = {
-      GetReport(impression_time, conversion_time,
-                /*expiry=*/base::TimeDelta::FromDays(9))};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
+  ConversionReport report = GetReport(impression_time, conversion_time,
+                                      /*expiry=*/base::TimeDelta::FromDays(9));
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
 
   // The expiry window is reported one hour after expiry time.
   EXPECT_EQ(impression_time + base::TimeDelta::FromDays(9) +
                 base::TimeDelta::FromHours(1),
-            reports[0].report_time);
+            report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
        SourceTypeEvent_ExpiryLessThanTwoDays_TwoDaysUsed) {
   base::Time impression_time = base::Time::Now();
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(3);
-  std::vector<ConversionReport> reports = {GetReport(
-      impression_time, conversion_time, /*expiry=*/base::TimeDelta::FromDays(1),
-      StorableImpression::SourceType::kEvent)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
+  ConversionReport report = GetReport(impression_time, conversion_time,
+                                      /*expiry=*/base::TimeDelta::FromDays(1),
+                                      StorableImpression::SourceType::kEvent);
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
   EXPECT_EQ(impression_time + base::TimeDelta::FromDays(2) +
                 base::TimeDelta::FromHours(1),
-            reports[0].report_time);
+            report.report_time);
 }
 
 TEST_F(ConversionStorageDelegateImplTest,
        SourceTypeEvent_ExpiryGreaterThanTwoDays_ExpiryUsed) {
   base::Time impression_time = base::Time::Now();
   base::Time conversion_time = impression_time + base::TimeDelta::FromDays(3);
-  std::vector<ConversionReport> reports = {GetReport(
-      impression_time, conversion_time, /*expiry=*/base::TimeDelta::FromDays(4),
-      StorableImpression::SourceType::kEvent)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
+  ConversionReport report = GetReport(impression_time, conversion_time,
+                                      /*expiry=*/base::TimeDelta::FromDays(4),
+                                      StorableImpression::SourceType::kEvent);
+  ConversionStorageDelegateImpl().ProcessNewConversionReport(report);
   EXPECT_EQ(impression_time + base::TimeDelta::FromDays(4) +
                 base::TimeDelta::FromHours(1),
-            reports[0].report_time);
-}
-
-TEST_F(ConversionStorageDelegateImplTest,
-       SingleReportForConversion_AttributionCreditAssigned) {
-  base::Time now = base::Time::Now();
-  std::vector<ConversionReport> reports = {
-      GetReport(/*impression_time=*/now, /*conversion_time=*/now)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(1u, reports.size());
-  EXPECT_EQ(100, reports[0].attribution_credit);
-}
-
-TEST_F(ConversionStorageDelegateImplTest,
-       TwoReportsForConversion_LastReceivesCredit) {
-  base::Time now = base::Time::Now();
-  std::vector<ConversionReport> reports = {
-      GetReport(/*impression_time=*/now, /*conversion_time=*/now),
-      GetReport(/*impression_time=*/now + base::TimeDelta::FromHours(100),
-                /*conversion_time=*/now)};
-  ConversionStorageDelegateImpl().ProcessNewConversionReports(&reports);
-  EXPECT_EQ(2u, reports.size());
-  EXPECT_EQ(0, reports[0].attribution_credit);
-  EXPECT_EQ(100, reports[1].attribution_credit);
-
-  // Ensure the reports were not rearranged.
-  EXPECT_EQ(now + base::TimeDelta::FromHours(100),
-            reports[1].impression.impression_time());
+            report.report_time);
 }
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql.cc b/content/browser/conversions/conversion_storage_sql.cc
index 6b861a7..87664bb1 100644
--- a/content/browser/conversions/conversion_storage_sql.cc
+++ b/content/browser/conversions/conversion_storage_sql.cc
@@ -54,11 +54,15 @@
 // Version 4 - 2021/03/16 - https://crrev.com/c/2716913
 //
 // Version 4 adds a new rate_limits table.
-const int kCurrentVersionNumber = 4;
+//
+// Version 5 - 2021/04/30 - https://crrev.com/c/2860056
+//
+// Version 5 drops the conversions.attribution_credit column.
+const int kCurrentVersionNumber = 5;
 
 // Earliest version which can use a |kCurrentVersionNumber| database
 // without failing.
-const int kCompatibleVersionNumber = 3;
+const int kCompatibleVersionNumber = 5;
 
 // Latest version of the database that cannot be upgraded to
 // |kCurrentVersionNumber| without razing the database. No versions are
@@ -222,13 +226,8 @@
   statement.BindTime(2, current_time);
   statement.BindInt(3, static_cast<int>(kSourceType));
 
-  // Create a set of default reports to add to storage.
-  std::vector<ConversionReport> new_reports;
+  std::vector<StorableImpression> impressions;
 
-  // We may have multiple impressions with the same impression origin be
-  // returned from the database. Cache results from |IsAttributionAllowed| to
-  // prevent duplicate lookups.
-  base::flat_map<url::Origin, bool> attribution_allowed_cache;
   while (statement.Step()) {
     int64_t impression_id = statement.ColumnInt64(0);
     std::string impression_data = statement.ColumnString(1);
@@ -247,39 +246,26 @@
     StorableImpression impression(
         impression_data, impression_origin, conversion_origin, reporting_origin,
         impression_time, expiry_time, kSourceType, impression_id);
-
-    ConversionReport report(std::move(impression), conversion.conversion_data(),
-                            /*conversion_time=*/current_time,
-                            /*report_time=*/current_time,
-                            /*conversion_id=*/base::nullopt);
-    auto cached_attribution_allowed =
-        attribution_allowed_cache.find(report.impression.impression_origin());
-    if (cached_attribution_allowed == attribution_allowed_cache.end()) {
-      bool allowed = rate_limit_table_.IsAttributionAllowed(db_.get(), report,
-                                                            current_time);
-      cached_attribution_allowed = attribution_allowed_cache.insert_or_assign(
-          attribution_allowed_cache.end(),
-          report.impression.impression_origin(), allowed);
-    }
-    if (cached_attribution_allowed->second)
-      new_reports.push_back(std::move(report));
+    impressions.push_back(std::move(impression));
   }
 
-  // Exit early if the last statement wasn't valid or if we have no new reports.
-  if (!statement.Succeeded() || new_reports.empty())
+  // Exit early if the last statement wasn't valid or if we have no impressions.
+  if (!statement.Succeeded() || impressions.empty())
     return 0;
 
-  // Exit early if the number of new reports exceeds the capacity for storing
-  // conversions per impression.
-  if (static_cast<int>(new_reports.size()) > capacity)
-    return 0;
+  const StorableImpression& impression_to_attribute =
+      delegate_->GetImpressionToAttribute(impressions);
 
-  // Allow the delegate to make arbitrary changes to the new conversion reports
-  // before we add them storage.
-  delegate_->ProcessNewConversionReports(&new_reports);
+  ConversionReport report(impression_to_attribute, conversion.conversion_data(),
+                          /*conversion_time=*/current_time,
+                          /*report_time=*/current_time,
+                          /*conversion_id=*/base::nullopt);
 
-  // |delegate_| may have removed all reports at this point.
-  if (new_reports.empty())
+  // Allow the delegate to make arbitrary changes to the new conversion report
+  // before we add it storage.
+  delegate_->ProcessNewConversionReport(report);
+
+  if (!rate_limit_table_.IsAttributionAllowed(db_.get(), report, current_time))
     return 0;
 
   sql::Transaction transaction(db_.get());
@@ -288,10 +274,16 @@
 
   const char kStoreConversionSql[] =
       "INSERT INTO conversions "
-      "(impression_id, conversion_data, conversion_time, report_time, "
-      "attribution_credit) VALUES(?,?,?,?,?)";
+      "(impression_id, conversion_data, conversion_time, report_time) "
+      "VALUES(?,?,?,?)";
   sql::Statement store_conversion_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kStoreConversionSql));
+  store_conversion_statement.BindInt64(0, *report.impression.impression_id());
+  store_conversion_statement.BindString(1, report.conversion_data);
+  store_conversion_statement.BindTime(2, current_time);
+  store_conversion_statement.BindTime(3, report.report_time);
+  if (!store_conversion_statement.Run())
+    return 0;
 
   // Mark impressions inactive if they hit the max conversions allowed limit
   // supplied by the delegate. Because only active impressions log conversions,
@@ -313,42 +305,39 @@
   int max_prior_conversions_before_inactive =
       delegate_->GetMaxConversionsPerImpression(kSourceType) - 1;
 
-  base::flat_set<url::Origin> unique_impression_origins;
-  for (const ConversionReport& report : new_reports) {
-    // Insert each report into the conversions table.
-    store_conversion_statement.Reset(/*clear_bound_vars=*/true);
-    store_conversion_statement.BindInt64(0, *report.impression.impression_id());
-    store_conversion_statement.BindString(1, report.conversion_data);
-    store_conversion_statement.BindTime(2, current_time);
-    store_conversion_statement.BindTime(3, report.report_time);
-    store_conversion_statement.BindInt(4, report.attribution_credit);
-    store_conversion_statement.Run();
+  // Update the attributed impression.
+  impression_update_statement.BindInt(0, max_prior_conversions_before_inactive);
+  impression_update_statement.BindInt64(1, *report.impression.impression_id());
+  if (!impression_update_statement.Run())
+    return 0;
 
-    // Update each associated impression.
-    impression_update_statement.Reset(/*clear_bound_vars=*/true);
-    impression_update_statement.BindInt(0,
-                                        max_prior_conversions_before_inactive);
-    impression_update_statement.BindInt64(1,
-                                          *report.impression.impression_id());
-    impression_update_statement.Run();
+  // Delete all unattributed impressions.
+  const char kDeleteUnattributedImpressionsSql[] =
+      "DELETE FROM impressions WHERE impression_id = ?";
+  sql::Statement delete_impression_statement(db_->GetCachedStatement(
+      SQL_FROM_HERE, kDeleteUnattributedImpressionsSql));
 
-    // We want to rate limit the total amount of data from a
-    // conversion_destination which can be joined to an impression_origin.
-    // Because we may have multiple reports for the same impression_origin here
-    // which share the same conversion data, guarantee only one of them
-    // contributes to the <impression_origin, conversion_destination> limit (the
-    // duplicate metadata does not leak more information when sent multiple
-    // times).
-    if (unique_impression_origins.insert(report.impression.impression_origin())
-            .second) {
-      if (!rate_limit_table_.AddRateLimit(db_.get(), report))
-        return 0;
-    }
+  for (const StorableImpression& impression : impressions) {
+    if (impression.impression_id() == *impression_to_attribute.impression_id())
+      continue;
+    delete_impression_statement.Reset(/*clear_bound_vars=*/true);
+    delete_impression_statement.BindInt64(0, *impression.impression_id());
+    if (!delete_impression_statement.Run())
+      return 0;
+    // Based on the deletion logic here and the fact that we delete impressions
+    // with |num_conversions > 1| when there is a new matching impression in
+    // |StoreImpression()|, we should be guaranteed that these impressions all
+    // have |num_conversions == 0|, and that they never contributed to a rate
+    // limit. Therefore, we don't need to call
+    // |RateLimitTable::ClearDataForImpressionIds()| here.
   }
 
+  if (!rate_limit_table_.AddRateLimit(db_.get(), report))
+    return 0;
+
   if (!transaction.Commit())
     return 0;
-  return new_reports.size();
+  return 1;
 }
 
 std::vector<ConversionReport> ConversionStorageSql::GetConversionsToReport(
@@ -360,7 +349,7 @@
   // Get all entries in the conversions table with a |report_time| less than
   // |expired_at| and their matching information from the impression table.
   const char kGetExpiredConversionsSql[] =
-      "SELECT C.conversion_data, C.attribution_credit, C.conversion_time, "
+      "SELECT C.conversion_data, C.conversion_time, "
       "C.report_time, "
       "C.conversion_id, I.impression_origin, I.conversion_origin, "
       "I.reporting_origin, I.impression_data, I.impression_time, "
@@ -374,21 +363,20 @@
   std::vector<ConversionReport> conversions;
   while (statement.Step()) {
     std::string conversion_data = statement.ColumnString(0);
-    int attribution_credit = statement.ColumnInt(1);
-    base::Time conversion_time = statement.ColumnTime(2);
-    base::Time report_time = statement.ColumnTime(3);
-    int64_t conversion_id = statement.ColumnInt64(4);
+    base::Time conversion_time = statement.ColumnTime(1);
+    base::Time report_time = statement.ColumnTime(2);
+    int64_t conversion_id = statement.ColumnInt64(3);
     url::Origin impression_origin =
-        DeserializeOrigin(statement.ColumnString(5));
+        DeserializeOrigin(statement.ColumnString(4));
     url::Origin conversion_origin =
-        DeserializeOrigin(statement.ColumnString(6));
-    url::Origin reporting_origin = DeserializeOrigin(statement.ColumnString(7));
-    std::string impression_data = statement.ColumnString(8);
-    base::Time impression_time = statement.ColumnTime(9);
-    base::Time expiry_time = statement.ColumnTime(10);
-    int64_t impression_id = statement.ColumnInt64(11);
+        DeserializeOrigin(statement.ColumnString(5));
+    url::Origin reporting_origin = DeserializeOrigin(statement.ColumnString(6));
+    std::string impression_data = statement.ColumnString(7);
+    base::Time impression_time = statement.ColumnTime(8);
+    base::Time expiry_time = statement.ColumnTime(9);
+    int64_t impression_id = statement.ColumnInt64(10);
     StorableImpression::SourceType source_type =
-        static_cast<StorableImpression::SourceType>(statement.ColumnInt(12));
+        static_cast<StorableImpression::SourceType>(statement.ColumnInt(11));
 
     // Ensure origins are valid before continuing. This could happen if there is
     // database corruption.
@@ -407,7 +395,6 @@
 
     ConversionReport report(std::move(impression), conversion_data,
                             conversion_time, report_time, conversion_id);
-    report.attribution_credit = attribution_credit;
 
     conversions.push_back(std::move(report));
   }
@@ -988,16 +975,14 @@
   // |conversion_time| is the time at which the conversion was registered, and
   // should be used for clearing site data. |report_time| is the time a
   // <conversion, impression> pair should be reported, and is specified by
-  // |delegate_|. |attribution_credit| is assigned by |delegate_| based on the
-  // set of impressions returned from |kGetMatchingImpressionsSql|.
+  // |delegate_|.
   const char kConversionTableSql[] =
       "CREATE TABLE IF NOT EXISTS conversions "
       "(conversion_id INTEGER PRIMARY KEY,"
       " impression_id INTEGER,"
       " conversion_data TEXT NOT NULL,"
       " conversion_time INTEGER NOT NULL,"
-      " report_time INTEGER NOT NULL,"
-      " attribution_credit INTEGER NOT NULL)";
+      " report_time INTEGER NOT NULL)";
   if (!db_->Execute(kConversionTableSql))
     return false;
 
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.cc b/content/browser/conversions/conversion_storage_sql_migrations.cc
index 04af609..5509471b 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations.cc
@@ -82,6 +82,10 @@
     if (!MigrateToVersion4(conversion_storage, db, meta_table))
       return false;
   }
+  if (meta_table->GetVersionNumber() == 4) {
+    if (!MigrateToVersion5(conversion_storage, db, meta_table))
+      return false;
+  }
   // Add similar if () blocks for new versions here.
 
   base::UmaHistogramMediumTimes("Conversions.Storage.MigrationTime",
@@ -309,4 +313,30 @@
   return transaction.Commit();
 }
 
+bool ConversionStorageSqlMigrations::MigrateToVersion5(
+    ConversionStorageSql* conversion_storage,
+    sql::Database* db,
+    sql::MetaTable* meta_table) {
+  // Wrap each migration in its own transaction. See comment in
+  // |MigrateToVersion2|.
+  sql::Transaction transaction(db);
+  if (!transaction.Begin())
+    return false;
+
+  // Any corresponding impressions will naturally be cleaned up by the expiry
+  // logic.
+  const char kDropZeroCreditConversionsSql[] =
+      "DELETE FROM conversions WHERE attribution_credit = 0";
+  if (!db->Execute(kDropZeroCreditConversionsSql))
+    return false;
+
+  const char kDropAttributionCreditColumnSql[] =
+      "ALTER TABLE conversions DROP COLUMN attribution_credit";
+  if (!db->Execute(kDropAttributionCreditColumnSql))
+    return false;
+
+  meta_table->SetVersionNumber(5);
+  return transaction.Commit();
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.h b/content/browser/conversions/conversion_storage_sql_migrations.h
index dff0fc2b..0c2efcf 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.h
+++ b/content/browser/conversions/conversion_storage_sql_migrations.h
@@ -64,6 +64,9 @@
   static bool MigrateToVersion4(ConversionStorageSql* conversion_storage,
                                 sql::Database* db,
                                 sql::MetaTable* meta_table);
+  static bool MigrateToVersion5(ConversionStorageSql* conversion_storage,
+                                sql::Database* db,
+                                sql::MetaTable* meta_table);
 };
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
index bf79197c..887f638c 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
@@ -29,7 +29,7 @@
   return output;
 }
 
-const int kCurrentVersionNumber = 4;
+const int kCurrentVersionNumber = 5;
 
 }  // namespace
 
@@ -57,7 +57,7 @@
   std::string GetCurrentSchema() {
     base::FilePath current_version_path = temp_directory_.GetPath().Append(
         FILE_PATH_LITERAL("TestCurrentVersion.db"));
-    LoadDatabase(FILE_PATH_LITERAL("version_4.sql"), current_version_path);
+    LoadDatabase(FILE_PATH_LITERAL("version_5.sql"), current_version_path);
     sql::Database db;
     EXPECT_TRUE(db.Open(current_version_path));
     return db.GetSchema();
@@ -289,4 +289,64 @@
   histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
 }
 
+TEST_F(ConversionStorageSqlMigrationsTest, MigrateVersion4ToCurrent) {
+  base::HistogramTester histograms;
+  LoadDatabase(FILE_PATH_LITERAL("version_4.sql"), DbPath());
+
+  // Verify pre-conditions.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    ASSERT_TRUE(db.DoesColumnExist("conversions", "attribution_credit"));
+
+    sql::Statement s(db.GetUniqueStatement(
+        "SELECT conversion_data FROM conversions ORDER BY conversion_id"));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("a", s.ColumnString(0));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("b", s.ColumnString(0));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("c", s.ColumnString(0));
+    ASSERT_FALSE(s.Step());
+  }
+
+  MigrateDatabase();
+
+  // Verify schema is current.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    // Check version.
+    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
+
+    // Check that expected tables are present.
+    EXPECT_TRUE(db.DoesTableExist("conversions"));
+    EXPECT_TRUE(db.DoesTableExist("impressions"));
+    EXPECT_TRUE(db.DoesTableExist("meta"));
+    EXPECT_TRUE(db.DoesTableExist("rate_limits"));
+
+    // Check that the expected column is dropped.
+    EXPECT_FALSE(db.DoesColumnExist("conversions", "attribution_credit"));
+
+    // Check that only the 0-credit conversions are deleted.
+    sql::Statement s(db.GetUniqueStatement(
+        "SELECT conversion_data FROM conversions ORDER BY conversion_id"));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("a", s.ColumnString(0));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("c", s.ColumnString(0));
+    ASSERT_FALSE(s.Step());
+
+    // Compare without quotes as sometimes migrations cause table names to be
+    // string literals.
+    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
+  }
+
+  // DB migration histograms should be recorded.
+  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
+  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_unittest.cc b/content/browser/conversions/conversion_storage_sql_unittest.cc
index bca058c..3bc3b37 100644
--- a/content/browser/conversions/conversion_storage_sql_unittest.cc
+++ b/content/browser/conversions/conversion_storage_sql_unittest.cc
@@ -255,10 +255,10 @@
   }
 
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
   clock()->Advance(base::TimeDelta::FromDays(1));
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
   storage()->ClearData(base::Time::Min(), base::Time::Max(), null_filter);
@@ -282,9 +282,9 @@
   EXPECT_EQ(0u, rate_limit_rows);
 
   histograms.ExpectUniqueSample(
-      "Conversions.ImpressionsDeletedInDataClearOperation", 10, 1);
+      "Conversions.ImpressionsDeletedInDataClearOperation", 1, 1);
   histograms.ExpectUniqueSample(
-      "Conversions.ReportsDeletedInDataClearOperation", 20, 1);
+      "Conversions.ReportsDeletedInDataClearOperation", 2, 1);
 }
 
 TEST_F(ConversionStorageSqlTest, MaxImpressionsPerOrigin) {
@@ -294,14 +294,14 @@
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   EXPECT_EQ(
-      2, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   CloseDatabase();
   sql::Database raw_db;
   EXPECT_TRUE(raw_db.Open(db_path()));
   size_t impression_rows;
   sql::test::CountTableRows(&raw_db, "impressions", &impression_rows);
-  EXPECT_EQ(2u, impression_rows);
+  EXPECT_EQ(1u, impression_rows);
   size_t rate_limit_rows;
   sql::test::CountTableRows(&raw_db, "rate_limits", &rate_limit_rows);
   EXPECT_EQ(1u, rate_limit_rows);
diff --git a/content/browser/conversions/conversion_storage_unittest.cc b/content/browser/conversions/conversion_storage_unittest.cc
index 7831f38d..1035bf0 100644
--- a/content/browser/conversions/conversion_storage_unittest.cc
+++ b/content/browser/conversions/conversion_storage_unittest.cc
@@ -37,8 +37,6 @@
 // Default delay in milliseconds for when a report should be sent for testing.
 const int kReportTime = 5;
 
-using AttributionCredits = std::list<int>;
-
 base::RepeatingCallback<bool(const url::Origin&)> GetMatcher(
     const url::Origin& to_delete) {
   return base::BindRepeating(std::equal_to<url::Origin>(), to_delete);
@@ -63,14 +61,12 @@
   // Given a |conversion|, returns the expected conversion report properties at
   // the current timestamp.
   ConversionReport GetExpectedReport(const StorableImpression& impression,
-                                     const StorableConversion& conversion,
-                                     int attribution_credit = 0) {
+                                     const StorableConversion& conversion) {
     ConversionReport report(impression, conversion.conversion_data(),
                             /*conversion_time=*/clock_.Now(),
                             /*report_time=*/clock_.Now() +
                                 base::TimeDelta::FromMilliseconds(kReportTime),
                             base::nullopt /* conversion_id */);
-    report.attribution_credit = attribution_credit;
     return report;
   }
 
@@ -80,10 +76,6 @@
     }
   }
 
-  void AddAttributionCredits(AttributionCredits credits) {
-    delegate_->AddCredits(credits);
-  }
-
   base::SimpleTestClock* clock() { return &clock_; }
 
   ConversionStorage* storage() { return storage_.get(); }
@@ -159,11 +151,11 @@
       1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 }
 
-TEST_F(ConversionStorageTest, MultipleImpressionsForConversion_AllConvert) {
+TEST_F(ConversionStorageTest, MultipleImpressionsForConversion_OneConverts) {
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   EXPECT_EQ(
-      2, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 }
 
 TEST_F(ConversionStorageTest,
@@ -283,25 +275,6 @@
   EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
 }
 
-TEST_F(ConversionStorageTest, OneConversion_AttributionCreditSet) {
-  auto impression = ImpressionBuilder(clock()->Now()).Build();
-  auto conversion = DefaultConversion();
-
-  const int kAttributionCredit = 100;
-  AddAttributionCredits({kAttributionCredit});
-
-  storage()->StoreImpression(impression);
-  EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
-
-  ConversionReport expected_report =
-      GetExpectedReport(impression, conversion, kAttributionCredit);
-  clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
-
-  std::vector<ConversionReport> actual_reports =
-      storage()->GetConversionsToReport(clock()->Now());
-  EXPECT_TRUE(ReportsEqual({expected_report}, actual_reports));
-}
-
 TEST_F(ConversionStorageTest,
        ExpiredImpressionWithPendingConversion_NotDeleted) {
   storage()->StoreImpression(
@@ -370,7 +343,7 @@
 }
 
 TEST_F(ConversionStorageTest,
-       ManyImpressionsWithManyConversions_ConversionReportsCreated) {
+       ManyImpressionsWithManyConversions_OneImpressionAttributed) {
   const int kNumMultiTouchImpressions = 20;
 
   // Store a large, arbitrary number of impressions.
@@ -379,9 +352,8 @@
   }
 
   for (int i = 0; i < kMaxConversions; i++) {
-    EXPECT_EQ(
-        kNumMultiTouchImpressions,
-        storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+    EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(
+                     DefaultConversion()));
   }
 
   // No additional conversion reports should be created for any of the
@@ -391,7 +363,7 @@
 }
 
 TEST_F(ConversionStorageTest,
-       NewImpressionForUnconvertedImpression_ImpressionRemainsActive) {
+       MultipleImpressionsForConversion_UnattributedImpressionsInactive) {
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
 
   auto new_impression =
@@ -402,8 +374,10 @@
 
   // The first impression should be active because even though
   // <reporting_origin, conversion_origin> matches, it has not converted yet.
+  EXPECT_EQ(2u, storage()->GetActiveImpressions().size());
   EXPECT_EQ(
-      2, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+  EXPECT_EQ(1u, storage()->GetActiveImpressions().size());
 }
 
 // This test makes sure that when a new click is received for a given
@@ -477,7 +451,7 @@
 
 TEST_F(
     ConversionStorageTest,
-    MultipleImpressionsForConversionAtDifferentTimes_AllImpressionsConverted) {
+    MultipleImpressionsForConversionAtDifferentTimes_OneImpressionAttributed) {
   auto first_impression = ImpressionBuilder(clock()->Now()).Build();
   storage()->StoreImpression(first_impression);
 
@@ -485,10 +459,6 @@
   storage()->StoreImpression(second_impression);
 
   auto conversion = DefaultConversion();
-  ConversionReport first_expected_conversion =
-      GetExpectedReport(first_impression, conversion);
-  ConversionReport second_expected_conversion =
-      GetExpectedReport(second_impression, conversion);
 
   // Advance clock so third impression is stored at a different timestamp.
   clock()->Advance(base::TimeDelta::FromMilliseconds(3));
@@ -500,13 +470,11 @@
 
   ConversionReport third_expected_conversion =
       GetExpectedReport(third_impression, conversion);
-  EXPECT_EQ(3, storage()->MaybeCreateAndStoreConversionReports(conversion));
+  EXPECT_EQ(1, storage()->MaybeCreateAndStoreConversionReports(conversion));
 
   clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
 
-  std::vector<ConversionReport> expected_reports = {first_expected_conversion,
-                                                    second_expected_conversion,
-                                                    third_expected_conversion};
+  std::vector<ConversionReport> expected_reports = {third_expected_conversion};
   std::vector<ConversionReport> actual_reports =
       storage()->GetConversionsToReport(clock()->Now());
 
@@ -514,7 +482,7 @@
 }
 
 TEST_F(ConversionStorageTest,
-       ImpressionsAtDifferentTimes_ReportedAtDifferentTimes) {
+       ImpressionsAtDifferentTimes_AttributedImpressionHasCorrectReportTime) {
   auto first_impression = ImpressionBuilder(clock()->Now()).Build();
   storage()->StoreImpression(first_impression);
 
@@ -526,18 +494,18 @@
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
 
   EXPECT_EQ(
-      3, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   // Advance to the first impression's report time and verify only its report is
   // available.
   clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime - 6));
+  EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
+
+  clock()->Advance(base::TimeDelta::FromMilliseconds(3));
+  EXPECT_EQ(0u, storage()->GetConversionsToReport(clock()->Now()).size());
+
+  clock()->Advance(base::TimeDelta::FromMilliseconds(3));
   EXPECT_EQ(1u, storage()->GetConversionsToReport(clock()->Now()).size());
-
-  clock()->Advance(base::TimeDelta::FromMilliseconds(3));
-  EXPECT_EQ(2u, storage()->GetConversionsToReport(clock()->Now()).size());
-
-  clock()->Advance(base::TimeDelta::FromMilliseconds(3));
-  EXPECT_EQ(3u, storage()->GetConversionsToReport(clock()->Now()).size());
 }
 
 TEST_F(ConversionStorageTest, GetConversionsToReportMultipleTimes_SameResult) {
@@ -557,53 +525,22 @@
   EXPECT_TRUE(ReportsEqual(first_call_reports, second_call_reports));
 }
 
-TEST_F(ConversionStorageTest,
-       ManyImpressionsWithAttributionCredits_CreditsAssignedCorrectly) {
-  const int kNumImpressions = 10;
-  std::vector<ConversionReport> expected_reports;
-  AttributionCredits credits;
-  auto conversion = DefaultConversion();
-
-  // Store a large, arbitrary number of impressions.
-  for (int i = 0; i < kNumImpressions; i++) {
-    auto impression = ImpressionBuilder(clock()->Now())
-                          .SetData(base::NumberToString(i))
-                          .Build();
-    storage()->StoreImpression(impression);
-    expected_reports.push_back(GetExpectedReport(impression, conversion, i));
-    credits.push_back(i);
-  }
-
-  // Add the expected credits to the delegate.
-  AddAttributionCredits(credits);
-  EXPECT_EQ(kNumImpressions,
-            storage()->MaybeCreateAndStoreConversionReports(conversion));
-
-  // Verify that the attribution credits were associated with scheduled
-  // conversions as expected.
-  clock()->Advance(base::TimeDelta::FromMilliseconds(kReportTime));
-  std::vector<ConversionReport> actual_reports =
-      storage()->GetConversionsToReport(clock()->Now());
-  EXPECT_TRUE(ReportsEqual(expected_reports, actual_reports));
-}
-
 TEST_F(ConversionStorageTest, MaxImpressionsPerOrigin) {
   delegate()->set_max_impressions_per_origin(2);
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   EXPECT_EQ(
-      2, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 }
 
 TEST_F(ConversionStorageTest, MaxConversionsPerOrigin) {
-  delegate()->set_max_conversions_per_origin(3);
+  delegate()->set_max_conversions_per_origin(1);
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   storage()->StoreImpression(ImpressionBuilder(clock()->Now()).Build());
   EXPECT_EQ(
-      2, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
-  // Verify that MaxConversionsPerOrigin is enforced when there are multiple
-  // impressions for a single conversion.
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+  // Verify that MaxConversionsPerOrigin is enforced.
   EXPECT_EQ(
       0, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 }
@@ -717,7 +654,7 @@
   auto conversion = DefaultConversion();
 
   std::vector<ConversionReport> expected_reports = {
-      GetExpectedReport(impression, conversion, 0)};
+      GetExpectedReport(impression, conversion)};
 
   storage()->StoreImpression(impression);
 
@@ -753,50 +690,13 @@
   storage()->StoreImpression(impression3);
 
   EXPECT_EQ(
-      3, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   // Only the first impression should overlap with this time range, but all the
   // impressions should share the origin.
   storage()->ClearData(start, start,
                        GetMatcher(impression1.impression_origin()));
-  EXPECT_EQ(2u, storage()->GetConversionsToReport(base::Time::Max()).size());
-}
-
-// Attribution occurs at conversion time, not report time, so deleted
-// impressions should not adjust credit allocation.
-TEST_F(ConversionStorageTest, ClearData_AttributionUnaffected) {
-  auto impression1 = ImpressionBuilder(clock()->Now())
-                         .SetData("xyz")
-                         .SetExpiry(base::TimeDelta::FromDays(30))
-                         .Build();
-  auto impression2 = ImpressionBuilder(clock()->Now())
-                         .SetData("abc")
-                         .SetExpiry(base::TimeDelta::FromDays(30))
-                         .Build();
-  auto conversion = DefaultConversion();
-  storage()->StoreImpression(impression1);
-  storage()->StoreImpression(impression2);
-  std::vector<ConversionReport> expected_reports = {
-      GetExpectedReport(impression1, conversion, 0),
-      GetExpectedReport(impression2, conversion, 0)};
-
-  clock()->Advance(base::TimeDelta::FromDays(1));
-  auto impression3 = ImpressionBuilder(clock()->Now())
-                         .SetExpiry(base::TimeDelta::FromDays(30))
-                         .Build();
-  storage()->StoreImpression(impression3);
-  base::Time delete_time = clock()->Now();
-  clock()->Advance(base::TimeDelta::FromDays(1));
-
-  AddAttributionCredits({100, 0, 0});
-  EXPECT_EQ(3, storage()->MaybeCreateAndStoreConversionReports(conversion));
-
-  // The last impression should be deleted, but the conversion shouldn't be.
-  storage()->ClearData(delete_time, delete_time,
-                       GetMatcher(impression1.impression_origin()));
-  std::vector<ConversionReport> actual_reports =
-      storage()->GetConversionsToReport(base::Time::Max());
-  EXPECT_TRUE(ReportsEqual(expected_reports, actual_reports));
+  EXPECT_EQ(1u, storage()->GetConversionsToReport(base::Time::Max()).size());
 }
 
 // The max time range with a null filter should delete everything.
@@ -811,10 +711,10 @@
   }
 
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
   clock()->Advance(base::TimeDelta::FromDays(1));
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
   storage()->ClearData(base::Time::Min(), base::Time::Max(), null_filter);
@@ -836,10 +736,10 @@
   }
 
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
   clock()->Advance(base::TimeDelta::FromDays(1));
   EXPECT_EQ(
-      10, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
+      1, storage()->MaybeCreateAndStoreConversionReports(DefaultConversion()));
 
   auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
   storage()->ClearData(base::Time(), base::Time::Max(), null_filter);
diff --git a/content/browser/conversions/conversion_test_utils.cc b/content/browser/conversions/conversion_test_utils.cc
index 3e5b4c2..de66455 100644
--- a/content/browser/conversions/conversion_test_utils.cc
+++ b/content/browser/conversions/conversion_test_utils.cc
@@ -5,6 +5,7 @@
 #include "content/browser/conversions/conversion_test_utils.h"
 
 #include <limits.h>
+#include <algorithm>
 
 #include <tuple>
 
@@ -89,20 +90,21 @@
 ConfigurableStorageDelegate::ConfigurableStorageDelegate() = default;
 ConfigurableStorageDelegate::~ConfigurableStorageDelegate() = default;
 
-void ConfigurableStorageDelegate::ProcessNewConversionReports(
-    std::vector<ConversionReport>* reports) {
-  // Note: reports are ordered by impression time, descending.
-  for (auto& report : *reports) {
-    report.report_time = report.impression.impression_time() +
-                         base::TimeDelta::FromMilliseconds(report_time_ms_);
+const StorableImpression& ConfigurableStorageDelegate::GetImpressionToAttribute(
+    const std::vector<StorableImpression>& impressions) {
+  DCHECK(!impressions.empty());
 
-    // If attribution credits were provided, associate them with reports
-    // in order.
-    if (!attribution_credits_.empty()) {
-      report.attribution_credit = attribution_credits_.front();
-      attribution_credits_.pop_front();
-    }
-  }
+  return *std::max_element(
+      impressions.begin(), impressions.end(),
+      [](const StorableImpression& a, const StorableImpression& b) {
+        return a.impression_time() < b.impression_time();
+      });
+}
+
+void ConfigurableStorageDelegate::ProcessNewConversionReport(
+    ConversionReport& report) {
+  report.report_time = report.impression.impression_time() +
+                       base::TimeDelta::FromMilliseconds(report_time_ms_);
 }
 int ConfigurableStorageDelegate::GetMaxConversionsPerImpression(
     StorableImpression::SourceType source_type) const {
@@ -287,8 +289,7 @@
                            conversion.impression.reporting_origin(),
                            conversion.impression.impression_time(),
                            conversion.impression.expiry_time(),
-                           conversion.conversion_data, conversion.report_time,
-                           conversion.attribution_credit);
+                           conversion.conversion_data, conversion.report_time);
   };
 
   if (expected.size() != actual.size())
diff --git a/content/browser/conversions/conversion_test_utils.h b/content/browser/conversions/conversion_test_utils.h
index 84fc2b1..9cfb360 100644
--- a/content/browser/conversions/conversion_test_utils.h
+++ b/content/browser/conversions/conversion_test_utils.h
@@ -74,13 +74,13 @@
 
 class ConfigurableStorageDelegate : public ConversionStorage::Delegate {
  public:
-  using AttributionCredits = std::list<int>;
   ConfigurableStorageDelegate();
   ~ConfigurableStorageDelegate() override;
 
   // ConversionStorage::Delegate
-  void ProcessNewConversionReports(
-      std::vector<ConversionReport>* reports) override;
+  const StorableImpression& GetImpressionToAttribute(
+      const std::vector<StorableImpression>& impressions) override;
+  void ProcessNewConversionReport(ConversionReport& report) override;
   int GetMaxConversionsPerImpression(
       StorableImpression::SourceType source_type) const override;
   int GetMaxImpressionsPerOrigin() const override;
@@ -105,11 +105,6 @@
     report_time_ms_ = report_time_ms;
   }
 
-  void AddCredits(AttributionCredits credits) {
-    // Add all credits to our list in order.
-    attribution_credits_.splice(attribution_credits_.end(), credits);
-  }
-
  private:
   int max_conversions_per_impression_ = INT_MAX;
   int max_impressions_per_origin_ = INT_MAX;
@@ -121,10 +116,6 @@
   };
 
   int report_time_ms_ = 0;
-
-  // List of attribution credits the test delegate should associate with
-  // reports.
-  AttributionCredits attribution_credits_;
 };
 
 // Test manager provider which can be used to inject a fake ConversionManager.
diff --git a/content/browser/conversions/conversions_browsertest.cc b/content/browser/conversions/conversions_browsertest.cc
index b49f66fb..cf0c8e7c 100644
--- a/content/browser/conversions/conversions_browsertest.cc
+++ b/content/browser/conversions/conversions_browsertest.cc
@@ -126,9 +126,8 @@
                        ImpressionConversion_ReportSent) {
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -165,9 +164,8 @@
                        WindowOpenDeprecatedAPI_NoException) {
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -207,9 +205,8 @@
                        WindowOpenImpressionConversion_ReportSent) {
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -243,9 +240,8 @@
 IN_PROC_BROWSER_TEST_F(ConversionsBrowserTest,
                        ImpressionFromCrossOriginSubframe_ReportSent) {
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -290,9 +286,8 @@
 IN_PROC_BROWSER_TEST_F(ConversionsBrowserTest,
                        ImpressionOnNoOpenerNavigation_ReportSent) {
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -330,9 +325,8 @@
                        ImpressionConversionSameDomain_ReportSent) {
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -372,9 +366,8 @@
     ConversionOnDifferentSubdomainThanLandingPage_ReportSent) {
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -421,12 +414,7 @@
   std::vector<ExpectedReportWaiter> expected_reports;
   expected_reports.emplace_back(
       GURL("https://d.test/.well-known/"
-           "register-conversion?impression-data=1&conversion-data=7&credit=0"),
-      https_server());
-  expected_reports.emplace_back(
-      GURL(
-          "https://d.test/.well-known/"
-          "register-conversion?impression-data=2&conversion-data=7&credit=100"),
+           "register-conversion?impression-data=2&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
@@ -487,9 +475,8 @@
 
   // Expected reports must be registered before the server starts.
   ExpectedReportWaiter expected_report(
-      GURL(
-          "https://a.test/.well-known/"
-          "register-conversion?impression-data=1&conversion-data=7&credit=100"),
+      GURL("https://a.test/.well-known/"
+           "register-conversion?impression-data=1&conversion-data=7"),
       https_server());
   ASSERT_TRUE(https_server()->Start());
 
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index b6452f0..f9b88c48 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -106,7 +106,7 @@
     return std::move(out_list);
   }
 
-  return value.CreateDeepCopy();
+  return base::Value::ToUniquePtrValue(value.Clone());
 }
 
 class DevToolsTraceEndpointProxy : public TracingController::TraceDataEndpoint {
diff --git a/content/browser/manifest/manifest_manager_host.cc b/content/browser/manifest/manifest_manager_host.cc
index 0b6a743..67e9d92 100644
--- a/content/browser/manifest/manifest_manager_host.cc
+++ b/content/browser/manifest/manifest_manager_host.cc
@@ -90,6 +90,7 @@
   if (!manifest_manager_frame_->IsCurrent())
     return;
 
+  manifest_manager_frame_->UpdateManifestURL(manifest_url);
   WebContents* web_contents =
       WebContents::FromRenderFrameHost(manifest_manager_frame_);
   static_cast<WebContentsImpl*>(web_contents)
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index dae12e0..15cab415 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -122,8 +122,8 @@
     prerender_helper_->WaitForRequest(url, count);
   }
 
-  void AddPrerender(const GURL& prerendering_url) {
-    prerender_helper_->AddPrerender(prerendering_url);
+  int AddPrerender(const GURL& prerendering_url) {
+    return prerender_helper_->AddPrerender(prerendering_url);
   }
 
   void NavigatePrimaryPage(const GURL& url) {
@@ -134,8 +134,9 @@
     return prerender_helper_->GetHostForUrl(url);
   }
 
-  RenderFrameHost* GetPrerenderedMainFrameHost(int host_id) {
-    return prerender_helper_->GetPrerenderedMainFrameHost(host_id);
+  RenderFrameHostImpl* GetPrerenderedMainFrameHost(int host_id) {
+    return static_cast<RenderFrameHostImpl*>(
+        prerender_helper_->GetPrerenderedMainFrameHost(host_id));
   }
 
   void NavigatePrerenderedPage(int host_id, const GURL& url) {
@@ -184,8 +185,7 @@
     ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
     // The initial page should not be in prerendered state.
-    RenderFrameHostImpl* initiator_render_frame_host =
-        static_cast<RenderFrameHostImpl*>(web_contents()->GetMainFrame());
+    RenderFrameHostImpl* initiator_render_frame_host = current_frame_host();
     EXPECT_EQ(initiator_render_frame_host->frame_tree()->type(),
               FrameTree::Type::kPrimary);
     EXPECT_EQ(initiator_render_frame_host->lifecycle_state(),
@@ -201,8 +201,7 @@
     EXPECT_EQ(web_contents()->GetURL(), prerender_url);
 
     // The activated page should no longer be in the prerendering state.
-    RenderFrameHostImpl* navigated_render_frame_host =
-        static_cast<RenderFrameHostImpl*>(web_contents()->GetMainFrame());
+    RenderFrameHostImpl* navigated_render_frame_host = current_frame_host();
     // The new page shouldn't be in the prerendering state.
     std::vector<RenderFrameHost*> frames =
         navigated_render_frame_host->GetFramesInSubtree();
@@ -393,7 +392,7 @@
   const GURL blob_gurl(blob_url);
 
   // Add <link rel=prerender> that will prerender the Blob page.
-  test::PrerenderHostRegistryObserver observer(web_contents_impl());
+  test::PrerenderHostRegistryObserver observer(*web_contents_impl());
   EXPECT_TRUE(ExecJs(web_contents(), JsReplace("add_prerender($1)", blob_url)));
   observer.WaitForTrigger(blob_gurl);
 
@@ -435,7 +434,7 @@
   const GURL blob_gurl(blob_url);
 
   // Add <link rel=prerender> that will prerender the Blob page.
-  test::PrerenderHostRegistryObserver observer(web_contents_impl());
+  test::PrerenderHostRegistryObserver observer(*web_contents_impl());
   EXPECT_TRUE(ExecJs(web_contents(), JsReplace("add_prerender($1)", blob_url)));
   observer.WaitForTrigger(blob_gurl);
 
@@ -477,12 +476,12 @@
   const GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html");
   const GURL kPrerenderingUrl =
       GetUrl("/server-redirect?" + kRedirectedUrl.spec());
-  test::PrerenderHostRegistryObserver registry_observer(web_contents_impl());
+  test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
   EXPECT_TRUE(
       ExecJs(web_contents(), JsReplace("add_prerender($1)", kPrerenderingUrl)));
   registry_observer.WaitForTrigger(kPrerenderingUrl);
   int host_id = GetHostForUrl(kPrerenderingUrl);
-  test::PrerenderHostObserver host_observer(web_contents_impl(), host_id);
+  test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
   host_observer.WaitForDestroyed();
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
   EXPECT_EQ(GetRequestCount(kRedirectedUrl), 0);
@@ -501,9 +500,7 @@
 
   // Start a prerender.
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
-  AddPrerender(kPrerenderingUrl);
-
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   // Attempt to activate the prerendered page for an iframe. This should fail
@@ -528,8 +525,7 @@
   // Start a prerender.
   const GURL kPrerenderingUrl =
       GetUrl("/prerender/add_prerender.html?prerender");
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   const GURL kSameOriginSubframeUrl =
@@ -612,8 +608,7 @@
   // Start a prerender.
   const GURL kPrerenderingUrl =
       GetUrl("/prerender/add_prerender.html?prerender");
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   const GURL kCrossOriginSubframeUrl =
@@ -681,12 +676,11 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
 
   // Start a navigation in the prerender frame tree that will cancel the
   // initiator's prerendering.
-  test::PrerenderHostObserver observer(web_contents_impl(), host_id);
+  test::PrerenderHostObserver observer(*web_contents_impl(), host_id);
 
   NavigatePrerenderedPage(host_id, kHungUrl);
 
@@ -710,8 +704,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
   WaitForPrerenderLoadCompleted(host_id);
 
@@ -736,8 +729,7 @@
 
   // Start a prerender.
   const GURL kPrerenderingUrl = GetUrl("/empty.html");
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   // Attempt to activate the prerendered page for a pop-up window. This should
@@ -761,7 +753,7 @@
   // Start a prerender.
   const GURL kPrerenderingUrl = GetUrl("/empty.html?next");
   AddPrerender(kPrerenderingUrl);
-  EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
+  ASSERT_TRUE(HasHostForUrl(kPrerenderingUrl));
 
   // Open a pop-up window.
   const GURL kWindowUrl = GetUrl("/empty.html?window");
@@ -901,8 +893,7 @@
   ASSERT_EQ(web_contents()->GetURL(), kInitialUrl);
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   RenderFrameHost* prerendered_render_frame_host =
       GetPrerenderedMainFrameHost(host_id);
   std::vector<RenderFrameHost*> frames =
@@ -976,10 +967,8 @@
   ASSERT_EQ(web_contents()->GetURL(), kInitialUrl);
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
-  auto* prerendered_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  const int host_id = AddPrerender(kPrerenderingUrl);
+  auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
   mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
       prerendered_render_frame_host
           ->browser_interface_broker_receiver_for_testing();
@@ -1019,10 +1008,8 @@
   ASSERT_EQ(web_contents()->GetURL(), kInitialUrl);
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
-  auto* main_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  const int host_id = AddPrerender(kPrerenderingUrl);
+  auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id);
   ASSERT_GE(main_render_frame_host->child_count(), 1U);
   RenderFrameHostImpl* child_render_frame_host =
       main_render_frame_host->child_at(0U)->current_frame_host();
@@ -1076,10 +1063,8 @@
       }));
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
-  auto* main_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  const int host_id = AddPrerender(kPrerenderingUrl);
+  auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Rebind a receiver for testing.
   // mojo::ReportBadMessage must be called within the stack frame derived from
@@ -1127,11 +1112,9 @@
   // Start a prerender.
   const GURL kPrerenderingUrl =
       GetUrl("/prerender/add_prerender.html?prerendering");
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerender_frame =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_frame = GetPrerenderedMainFrameHost(host_id);
 
   // Attempt to open a window in the prerendered page. This should fail.
   const GURL kWindowOpenUrl = GetUrl("/empty.html");
@@ -1153,13 +1136,11 @@
             LifecycleStateImpl::kActive);
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   // Open an iframe in the prerendered page.
-  RenderFrameHostImpl* rfh_a =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  RenderFrameHostImpl* rfh_a = GetPrerenderedMainFrameHost(host_id);
   EXPECT_EQ("LOADED",
             EvalJs(rfh_a, JsReplace("add_iframe($1)", GetUrl("/empty.html"))));
   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
@@ -1204,7 +1185,7 @@
     console_observer.SetPattern(kConsolePattern);
 
     // Prerender will fail. Then FindHostByUrlForTesting() should return null.
-    test::PrerenderHostRegistryObserver observer(web_contents_impl());
+    test::PrerenderHostRegistryObserver observer(*web_contents_impl());
     EXPECT_TRUE(
         ExecJs(web_contents(), JsReplace("add_prerender($1)", disallowed_url)));
     observer.WaitForTrigger(disallowed_url);
@@ -1255,7 +1236,7 @@
     GURL disallowed_url = GetUrl("/title1.html");
     WebContentsConsoleObserver console_observer(web_contents_impl());
     console_observer.SetPattern(kConsolePattern);
-    test::PrerenderHostRegistryObserver observer(web_contents_impl());
+    test::PrerenderHostRegistryObserver observer(*web_contents_impl());
     EXPECT_TRUE(
         ExecJs(web_contents(), JsReplace("add_prerender($1)", disallowed_url)));
     observer.WaitForTrigger(disallowed_url);
@@ -1300,11 +1281,9 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Make a prerendered page.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerender_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   EXPECT_EQ(
       true,
@@ -1342,11 +1321,9 @@
   ASSERT_EQ(web_contents()->GetURL(), kInitialUrl);
 
   // Start a prerender.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerender_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Get the DocumentData associated with prerender RenderFrameHost.
   DocumentData::CreateForCurrentDocument(prerender_render_frame_host);
@@ -1386,14 +1363,13 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Make a prerendered page.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Executing `navigator.getGamepads()` to start binding the GamepadMonitor
   // interface.
-  ignore_result(EvalJs(prerendered_render_frame_host, "navigator.getGamepads()",
+  ignore_result(EvalJs(prerender_render_frame_host, "navigator.getGamepads()",
                        EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
   // Verify Mojo capability control cancels prerendering.
   EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl));
@@ -1448,11 +1424,9 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Make a prerendered page.
-  AddPrerender(kPrerenderingUrl);
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerender_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Access the clipboard and fail.
   EXPECT_EQ(false,
@@ -1467,12 +1441,12 @@
 void LoadAndWaitForPrerenderDestroyed(WebContents* const web_contents,
                                       const GURL prerendering_url,
                                       test::PrerenderTestHelper* helper) {
-  test::PrerenderHostRegistryObserver registry_observer(web_contents);
+  test::PrerenderHostRegistryObserver registry_observer(*web_contents);
   EXPECT_TRUE(
       ExecJs(web_contents, JsReplace("add_prerender($1)", prerendering_url)));
   registry_observer.WaitForTrigger(prerendering_url);
   int host_id = helper->GetHostForUrl(prerendering_url);
-  test::PrerenderHostObserver host_observer(web_contents, host_id);
+  test::PrerenderHostObserver host_observer(*web_contents, host_id);
   host_observer.WaitForDestroyed();
   EXPECT_EQ(helper->GetHostForUrl(prerendering_url),
             RenderFrameHost::kNoFrameTreeNodeId);
@@ -1490,14 +1464,14 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   LoadAndWaitForPrerenderDestroyed(
-      shell()->web_contents(),
-      GetUrl("/prerender/page-with-embedded-plugin.html"), prerender_helper());
+      web_contents(), GetUrl("/prerender/page-with-embedded-plugin.html"),
+      prerender_helper());
   histogram_tester.ExpectUniqueSample(
       "Prerender.Experimental.PrerenderHostFinalStatus",
       PrerenderHost::FinalStatus::kPlugin, 1);
   LoadAndWaitForPrerenderDestroyed(
-      shell()->web_contents(),
-      GetUrl("/prerender/page-with-object-plugin.html"), prerender_helper());
+      web_contents(), GetUrl("/prerender/page-with-object-plugin.html"),
+      prerender_helper());
   histogram_tester.ExpectUniqueSample(
       "Prerender.Experimental.PrerenderHostFinalStatus",
       PrerenderHost::FinalStatus::kPlugin, 2);
@@ -1521,15 +1495,12 @@
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
   // Make a prerendered page.
-  AddPrerender(kPrerenderingUrl);
-
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerendered_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Create the Notification and fail.
-  EXPECT_EQ(false, EvalJs(prerendered_render_frame_host, R"(
+  EXPECT_EQ(false, EvalJs(prerender_render_frame_host, R"(
     (() => {
       try { new Notification('My Notification'); return true;
       } catch(e) { return false; }
@@ -1548,7 +1519,7 @@
   // Navigate to an initial page.
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
 
-  LoadAndWaitForPrerenderDestroyed(shell()->web_contents(),
+  LoadAndWaitForPrerenderDestroyed(web_contents(),
                                    GetUrl("/prerender/notification.html"),
                                    prerender_helper());
 
@@ -1574,7 +1545,7 @@
       switches::kEnableLowEndDeviceMode);
 
   // Attempt to prerender.
-  test::PrerenderHostRegistryObserver observer(web_contents_impl());
+  test::PrerenderHostRegistryObserver observer(*web_contents_impl());
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
   EXPECT_TRUE(
       ExecJs(web_contents(), JsReplace("add_prerender($1)", kPrerenderingUrl)));
@@ -1599,14 +1570,12 @@
 
   // Add <link rel=prerender> that will prerender `kPrerenderingUrl`.
   ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
-  AddPrerender(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
 
   // A prerender host for the URL should be registered.
-  const int host_id = GetHostForUrl(kPrerenderingUrl);
   ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
-  auto* prerender_render_frame_host =
-      static_cast<RenderFrameHostImpl*>(GetPrerenderedMainFrameHost(host_id));
+  auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id);
 
   // Invoke IsInactiveAndDisallowActivation for the prerendered document.
   EXPECT_EQ(prerender_render_frame_host->lifecycle_state(),
@@ -1642,10 +1611,8 @@
   SyntheticTapGestureParams params;
   params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
   params.position = GetCenterCoordinatesOfElementWithId(web_contents(), "link");
-  static_cast<RenderWidgetHostImpl*>(
-      web_contents()->GetRenderViewHost()->GetWidget())
-      ->QueueSyntheticGesture(std::make_unique<SyntheticTapGesture>(params),
-                              base::DoNothing());
+  web_contents_impl()->GetRenderViewHost()->GetWidget()->QueueSyntheticGesture(
+      std::make_unique<SyntheticTapGesture>(params), base::DoNothing());
   navigation_observer.Wait();
 
   EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl);
@@ -1661,16 +1628,14 @@
 
   // Add <link rel=prerender> that will prerender `kPrerenderingUrl`.
   ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
-  AddPrerender(kPrerenderingUrl);
+  const int host_id = AddPrerender(kPrerenderingUrl);
   EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
 
   // A prerender host for the URL should be registered.
-  EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
+  ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
 
   // The visibility state must be "hidden" while prerendering.
-  int host_id = GetHostForUrl(kPrerenderingUrl);
-  RenderFrameHost* prerendered_render_frame_host =
-      GetPrerenderedMainFrameHost(host_id);
+  auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id);
   auto* rvh = static_cast<RenderViewHostImpl*>(
       prerendered_render_frame_host->GetRenderViewHost());
   EXPECT_EQ(rvh->GetPageLifecycleStateManager()
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 1c65ce4..e106918 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -5687,6 +5687,11 @@
          frame_tree_node()->frame_tree()->type() == FrameTree::Type::kPrimary;
 }
 
+bool NavigationRequest::IsPrerenderedPageActivation() {
+  CHECK_GE(state_, WILL_START_REQUEST);
+  return prerender_frame_tree_node_id_ != RenderFrameHost::kNoFrameTreeNodeId;
+}
+
 int NavigationRequest::GetFrameTreeNodeId() {
   return frame_tree_node()->frame_tree_node_id();
 }
@@ -6372,17 +6377,13 @@
   }
 }
 
-bool NavigationRequest::IsPrerenderedPageActivation() const {
-  CHECK_GE(state_, WILL_START_REQUEST);
-  return prerender_frame_tree_node_id_ != RenderFrameHost::kNoFrameTreeNodeId;
-}
-
 bool NavigationRequest::IsServedFromBackForwardCache() const {
   return rfh_restored_from_back_forward_cache_ != nullptr;
 }
 
 bool NavigationRequest::IsPageActivation() const {
-  return IsPrerenderedPageActivation() || IsServedFromBackForwardCache();
+  return const_cast<NavigationRequest*>(this)->IsPrerenderedPageActivation() ||
+         IsServedFromBackForwardCache();
 }
 
 std::unique_ptr<NavigationEntryImpl>
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index fd2b6762..46fb388f 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -282,6 +282,7 @@
   SiteInstanceImpl* GetSourceSiteInstance() override;
   bool IsInMainFrame() override;
   bool IsInPrimaryMainFrame() override;
+  bool IsPrerenderedPageActivation() override;
   bool IsRendererInitiated() override;
   bool IsSameOrigin() override;
   bool WasServerRedirect() override;
@@ -850,11 +851,6 @@
   // navigation.
   const GURL& GetOriginalRequestURL();
 
-  // Prerender2:
-  // Returns true if this navigation will activate a prerendered page. It is
-  // only meaningful to call this after BeginNavigation().
-  bool IsPrerenderedPageActivation() const;
-
   // This is the same as |NavigationHandle::IsServedFromBackForwardCache|, but
   // adds a const qualifier.
   bool IsServedFromBackForwardCache() const;
diff --git a/content/browser/renderer_host/render_frame_host_delegate.h b/content/browser/renderer_host/render_frame_host_delegate.h
index cefb86f..2cf2b941 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.h
+++ b/content/browser/renderer_host/render_frame_host_delegate.h
@@ -197,7 +197,7 @@
   // the renderer process.
   virtual void UpdateFaviconURL(
       RenderFrameHostImpl* source,
-      std::vector<blink::mojom::FaviconURLPtr> candidates) {}
+      const std::vector<blink::mojom::FaviconURLPtr>& candidates) {}
 
   // The frame changed its window.name property.
   virtual void DidChangeName(RenderFrameHostImpl* render_frame_host,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 80e4a073..c1e98b143 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -1214,6 +1214,7 @@
       subframe_unload_timeout_(RenderViewHostImpl::kUnloadTimeout),
       media_device_id_salt_base_(
           BrowserContext::CreateRandomMediaDeviceIDSalt()),
+      document_associated_data_(std::make_unique<DocumentAssociatedData>()),
       lifecycle_state_(lifecycle_state) {
   DCHECK(delegate_);
   DCHECK(lifecycle_state_ == LifecycleStateImpl::kSpeculative ||
@@ -2711,7 +2712,7 @@
   const RenderFrameState old_render_frame_state = render_frame_state_;
   render_frame_state_ = RenderFrameState::kCreated;
 
-  // Clear all the user data associated with this RenderFrameHost when its
+  // Clear all the document-associated data for this RenderFrameHost when its
   // RenderFrame is recreated after a crash. Checking
   // |was_render_frame_ever_created_| guarantees that the user data isn't
   // cleared for the initial RenderFrame creation.  Note that the user data is
@@ -2722,7 +2723,7 @@
   // - a) new new state set in RenderFrameCreated doesn't get deleted.
   // - b) the old state is not leaked to a new RenderFrameHost.
   if (old_render_frame_state == RenderFrameState::kDeleted)
-    document_associated_data_.ClearAllUserData();
+    document_associated_data_ = std::make_unique<DocumentAssociatedData>();
 
   // Initialize the RenderWidgetHost which marks it and the RenderViewHost as
   // live before calling to the `delegate_`.
@@ -3984,6 +3985,13 @@
                                     std::move(dialog_closed_callback));
 }
 
+void RenderFrameHostImpl::UpdateFaviconURL(
+    std::vector<blink::mojom::FaviconURLPtr> favicon_urls) {
+  DCHECK(!GetParent());
+  document_associated_data_->favicon_urls = std::move(favicon_urls);
+  delegate_->UpdateFaviconURL(this, document_associated_data_->favicon_urls);
+}
+
 void RenderFrameHostImpl::ScaleFactorChanged(float scale) {
   delegate_->OnPageScaleFactorChanged(this, scale);
 }
@@ -4050,9 +4058,10 @@
   std::move(callback).Run();
 }
 
-void RenderFrameHostImpl::UpdateFaviconURL(
-    std::vector<blink::mojom::FaviconURLPtr> favicon_urls) {
-  delegate_->UpdateFaviconURL(this, std::move(favicon_urls));
+void RenderFrameHostImpl::UpdateManifestURL(
+    const base::Optional<GURL>& manifest_url) {
+  DCHECK(!GetParent());
+  document_associated_data_->manifest_url = manifest_url.value_or(GURL());
 }
 
 void RenderFrameHostImpl::DownloadURL(
@@ -4750,6 +4759,7 @@
 }
 
 void RenderFrameHostImpl::DocumentOnLoadCompleted() {
+  document_associated_data_->is_on_load_completed = true;
   // This message is only sent for top-level frames.
   //
   // TODO(avi): when frame tree mirroring works correctly, add a check here
@@ -9269,13 +9279,13 @@
     if (lifecycle_state() != LifecycleStateImpl::kPendingCommit &&
         !committed_speculative_rfh_before_navigation_commit_) {
       DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kSpeculative);
-      // Clear all the user data associated with the non-pending commit
+      // Clear all document-associated data for the non-pending commit
       // RenderFrameHosts because the navigation has created a new document.
       // Make sure the data doesn't get cleared for the cases when the
       // RenderFrameHost commits before the navigation commits. This happens
       // when the current RenderFrameHost crashes before navigating to a new
       // URL.
-      document_associated_data_.ClearAllUserData();
+      document_associated_data_ = std::make_unique<DocumentAssociatedData>();
     }
 
     // Continue observing the events for the committed navigation.
@@ -10973,6 +10983,22 @@
   GetPeerConnectionTrackerHost().StopEventLog(lid);
 }
 
+bool RenderFrameHostImpl::IsDocumentOnLoadCompletedInMainFrame() {
+  auto* main_frame = GetMainFrame();
+  return main_frame->document_associated_data_->is_on_load_completed;
+}
+
+const GURL& RenderFrameHostImpl::ManifestURL() {
+  auto* main_frame = GetMainFrame();
+  return main_frame->document_associated_data_->manifest_url;
+}
+
+const std::vector<blink::mojom::FaviconURLPtr>&
+RenderFrameHostImpl::FaviconURLs() {
+  auto* main_frame = GetMainFrame();
+  return main_frame->document_associated_data_->favicon_urls;
+}
+
 mojo::PendingRemote<network::mojom::CookieAccessObserver>
 RenderFrameHostImpl::CreateCookieAccessObserver() {
   mojo::PendingRemote<network::mojom::CookieAccessObserver> remote;
@@ -11222,6 +11248,10 @@
     commit_navigation_sent_counter_ = 0;
 }
 
+RenderFrameHostImpl::DocumentAssociatedData::DocumentAssociatedData() = default;
+RenderFrameHostImpl::DocumentAssociatedData::~DocumentAssociatedData() =
+    default;
+
 std::ostream& operator<<(std::ostream& o,
                          const RenderFrameHostImpl::LifecycleStateImpl& s) {
   return o << LifecycleStateImplToString(s);
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 9630c707..24dd5cd 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1885,6 +1885,8 @@
   void SetWindowRect(const gfx::Rect& bounds,
                      SetWindowRectCallback callback) override;
 
+  void UpdateManifestURL(const base::Optional<GURL>& manifest_url);
+
   void ReportNoBinderForInterface(const std::string& error);
 
   // Returns true if this object has any NavigationRequests matching |origin|.
@@ -1922,17 +1924,17 @@
   // content/public/browser/render_document_host_user_data.h for more details.
   base::SupportsUserData::Data* GetRenderDocumentHostUserData(
       const void* key) const {
-    return document_associated_data_.GetUserData(key);
+    return document_associated_data_->GetUserData(key);
   }
 
   void SetRenderDocumentHostUserData(
       const void* key,
       std::unique_ptr<base::SupportsUserData::Data> data) {
-    document_associated_data_.SetUserData(key, std::move(data));
+    document_associated_data_->SetUserData(key, std::move(data));
   }
 
   void RemoveRenderDocumentHostUserData(const void* key) {
-    document_associated_data_.RemoveUserData(key);
+    document_associated_data_->RemoveUserData(key);
   }
 
   // Called when we commit speculative RFH early due to not having an alive
@@ -2046,6 +2048,9 @@
       mojo::PendingReceiver<blink::mojom::PeerConnectionTrackerHost> receiver);
   void EnableWebRtcEventLogOutput(int lid, int output_period_ms) override;
   void DisableWebRtcEventLogOutput(int lid) override;
+  bool IsDocumentOnLoadCompletedInMainFrame() override;
+  const GURL& ManifestURL() override;
+  const std::vector<blink::mojom::FaviconURLPtr>& FaviconURLs() override;
 
 #if BUILDFLAG(ENABLE_PLUGINS)
   void PepperInstanceClosed(int32_t instance_id);
@@ -3531,12 +3536,30 @@
 
   // Container for arbitrary document-associated feature-specific data. Should
   // be reset when committing a cross-document navigation in this
-  // RenderFrameHost. Please refer to the description at
+  // RenderFrameHost. RenderFrameHostImpl stores internal members here
+  // directly while consumers of RenderFrameHostImpl should store data via
+  // GetRenderDocumentHostUserData(). Please refer to the description at
   // content/public/browser/render_document_host_user_data.h for more details.
-  class DocumentAssociatedData : public base::SupportsUserData {
-    friend class RenderFrameHostImpl;
+  struct DocumentAssociatedData : public base::SupportsUserData {
+    DocumentAssociatedData();
+    ~DocumentAssociatedData() override;
+    DocumentAssociatedData(const DocumentAssociatedData&) = delete;
+    DocumentAssociatedData& operator=(const DocumentAssociatedData&) = delete;
+
+    // True if we've received a notification that the onload() handler has
+    // run in the main frame.
+    bool is_on_load_completed = false;
+
+    // Web application manifest URL (or empty URL if none) for this main frame.
+    // See https://w3c.github.io/manifest/#web-application-manifest
+    GURL manifest_url;
+
+    // Candidate favicon URLs. Each main frame may have a collection and will
+    // be displayed when active (i.e., upon activation for prerendering).
+    std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
   };
-  DocumentAssociatedData document_associated_data_;
+
+  std::unique_ptr<DocumentAssociatedData> document_associated_data_;
 
   // Keeps track of the scenario when RenderFrameHostManager::CommitPending is
   // called before the navigation commits. This becomes true if the previous
diff --git a/content/browser/renderer_host/render_frame_host_impl_unittest.cc b/content/browser/renderer_host/render_frame_host_impl_unittest.cc
index 7eb01d4..f8d3710b 100644
--- a/content/browser/renderer_host/render_frame_host_impl_unittest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_unittest.cc
@@ -13,6 +13,7 @@
 #include "net/cookies/site_for_cookies.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -223,4 +224,59 @@
             network::mojom::ReferrerPolicy::kNever);
 }
 
+TEST_F(RenderFrameHostImplTest, FaviconURLsSet) {
+  TestRenderFrameHost* main_rfh = contents()->GetMainFrame();
+  const auto kFavicon =
+      blink::mojom::FaviconURL(GURL("https://example.com/favicon.ico"),
+                               blink::mojom::FaviconIconType::kFavicon, {});
+  std::unique_ptr<NavigationSimulator> navigation =
+      NavigationSimulator::CreateBrowserInitiated(GURL("https://example.com"),
+                                                  contents());
+  ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
+  navigation->SetTransition(transition);
+  navigation->Commit();
+  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
+
+  std::vector<blink::mojom::FaviconURLPtr> one_favicon_url;
+  one_favicon_url.push_back(blink::mojom::FaviconURL::New(kFavicon));
+  main_rfh->UpdateFaviconURL(std::move(one_favicon_url));
+  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
+
+  std::vector<blink::mojom::FaviconURLPtr> two_favicon_urls;
+  two_favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
+  two_favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
+  main_rfh->UpdateFaviconURL(std::move(two_favicon_urls));
+  EXPECT_EQ(2u, contents()->GetFaviconURLs().size());
+
+  std::vector<blink::mojom::FaviconURLPtr> another_one_favicon_url;
+  another_one_favicon_url.push_back(blink::mojom::FaviconURL::New(kFavicon));
+  main_rfh->UpdateFaviconURL(std::move(another_one_favicon_url));
+  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
+}
+
+TEST_F(RenderFrameHostImplTest, FaviconURLsResetWithNavigation) {
+  TestRenderFrameHost* main_rfh = contents()->GetMainFrame();
+  std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
+  favicon_urls.push_back(blink::mojom::FaviconURL::New(
+      GURL("https://example.com/favicon.ico"),
+      blink::mojom::FaviconIconType::kFavicon, std::vector<gfx::Size>()));
+
+  std::unique_ptr<NavigationSimulator> navigation =
+      NavigationSimulator::CreateBrowserInitiated(GURL("https://example.com"),
+                                                  contents());
+  ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
+  navigation->SetTransition(transition);
+  navigation->Commit();
+
+  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
+  main_rfh->UpdateFaviconURL(std::move(favicon_urls));
+  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
+
+  navigation = NavigationSimulator::CreateBrowserInitiated(
+      GURL("https://example.com/navigation.html"), contents());
+  navigation->SetTransition(transition);
+  navigation->Commit();
+  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
+}
+
 }  // namespace content
diff --git a/content/browser/resources/conversions/conversion_internals.html b/content/browser/resources/conversions/conversion_internals.html
index 8b1e7f21..fd2fcff 100644
--- a/content/browser/resources/conversions/conversion_internals.html
+++ b/content/browser/resources/conversions/conversion_internals.html
@@ -89,9 +89,6 @@
               Report Time
             </th>
             <th>
-              Attribution Credit
-            </th>
-            <th>
               Source Type
             </th>
           </tr>
@@ -125,7 +122,6 @@
     <td class="conversion-origin-cell"></td>
     <td class="reporting-origin-cell"></td>
     <td class="report-time-cell"></td>
-    <td class="attibution-credit-cell"></td>
     <td class="source-type-cell"></td>
   </tr>
 </template>
diff --git a/content/browser/resources/conversions/conversion_internals.js b/content/browser/resources/conversions/conversion_internals.js
index 4e54d70..8cb8afb 100644
--- a/content/browser/resources/conversions/conversion_internals.js
+++ b/content/browser/resources/conversions/conversion_internals.js
@@ -107,8 +107,7 @@
   td[2].textContent = UrlToText(report.conversionOrigin);
   td[3].textContent = UrlToText(report.reportingOrigin);
   td[4].textContent = new Date(report.reportTime).toLocaleString();
-  td[5].textContent = report.attributionCredit;
-  td[6].textContent = SourceTypeToText(report.sourceType);
+  td[5].textContent = SourceTypeToText(report.sourceType);
   return document.importNode(template.content, true);
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index cee128d..6b2e8b1 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1585,6 +1585,7 @@
 void WebContentsImpl::NotifyManifestUrlChanged(
     RenderFrameHost* rfh,
     const base::Optional<GURL>& manifest_url) {
+  // TODO(crbug.com/1201237): update the app manifest code for MPArch.
   OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::NotifyManifestUrlChanged",
                         "render_frame_host", rfh, "manifest_url", manifest_url);
   observers_.NotifyObservers(&WebContentsObserver::DidUpdateWebManifestURL, rfh,
@@ -3213,8 +3214,8 @@
   // required.
   if (!will_cause_resize) {
     if (RenderWidgetHostView* rwhv = GetRenderWidgetHostView()) {
-        if (RenderWidgetHost* render_widget_host = rwhv->GetRenderWidgetHost())
-          render_widget_host->SynchronizeVisualProperties();
+      if (RenderWidgetHost* render_widget_host = rwhv->GetRenderWidgetHost())
+        render_widget_host->SynchronizeVisualProperties();
     }
   }
 
@@ -4666,8 +4667,8 @@
           policy_exception_justification: "Not implemented."
         })");
   auto params = std::make_unique<download::DownloadUrlParameters>(
-      url, frame_host->GetProcess()->GetID(),
-      frame_host->GetRoutingID(), traffic_annotation);
+      url, frame_host->GetProcess()->GetID(), frame_host->GetRoutingID(),
+      traffic_annotation);
   params->set_referrer(referrer.url);
   params->set_referrer_policy(
       Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
@@ -5147,9 +5148,6 @@
 void WebContentsImpl::DidStartNavigation(NavigationHandle* navigation_handle) {
   TRACE_EVENT1("navigation", "WebContentsImpl::DidStartNavigation",
                "navigation_handle", navigation_handle);
-  if (navigation_handle->IsInMainFrame())
-    favicon_urls_.clear();
-
   {
     SCOPED_UMA_HISTOGRAM_TIMER("WebContentsObserver.DidStartNavigation");
     observers_.NotifyObservers(&WebContentsObserver::DidStartNavigation,
@@ -5339,6 +5337,16 @@
       SetWebPreferences(*web_preferences_.get());
     }
   }
+
+  if (navigation_handle->HasCommitted() &&
+      navigation_handle->IsPrerenderedPageActivation()) {
+    // We defer favicon URL updates while prerendering. Upon activation, we
+    // must inform interested parties about our candidate favicon URLs.
+    auto* rfhi = static_cast<RenderFrameHostImpl*>(
+        navigation_handle->GetRenderFrameHost());
+    if (!rfhi->GetParent())
+      UpdateFaviconURL(rfhi, rfhi->FaviconURLs());
+  }
 }
 
 void WebContentsImpl::DidFailLoadWithError(
@@ -6026,7 +6034,7 @@
 
 void WebContentsImpl::UpdateFaviconURL(
     RenderFrameHostImpl* source,
-    std::vector<blink::mojom::FaviconURLPtr> candidates) {
+    const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
   OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::UpdateFaviconURL",
                         "render_frame_host", source);
   // Ignore favicons for non-main frame.
@@ -6042,10 +6050,8 @@
   if (!source->IsCurrent())
     return;
 
-  favicon_urls_ = std::move(candidates);
-
   observers_.NotifyObservers(&WebContentsObserver::DidUpdateFaviconURL, source,
-                             favicon_urls_);
+                             candidates);
 }
 
 void WebContentsImpl::SetIsOverlayContent(bool is_overlay_content) {
@@ -6208,7 +6214,7 @@
   int type = is_loading ? NOTIFICATION_LOAD_START : NOTIFICATION_LOAD_STOP;
   NotificationDetails det = NotificationService::NoDetails();
   if (details)
-      det = Details<LoadNotificationDetails>(details);
+    det = Details<LoadNotificationDetails>(details);
   NotificationService::current()->Notify(
       type, Source<NavigationController>(&GetController()), det);
 }
@@ -7038,7 +7044,8 @@
   // immediately if enough time has passed.
   base::TimeDelta min_delay =
       base::TimeDelta::FromMilliseconds(kMinimumDelayBetweenLoadingUpdatesMS);
-  bool delay_elapsed = loading_last_progress_update_.is_null() ||
+  bool delay_elapsed =
+      loading_last_progress_update_.is_null() ||
       base::TimeTicks::Now() - loading_last_progress_update_ > min_delay;
 
   if (load_progress == 0.0 || load_progress == 1.0 || delay_elapsed) {
@@ -7170,10 +7177,9 @@
       render_frame_host);
 
   // TODO(avi): Remove. http://crbug.com/170921
-  NotificationService::current()->Notify(
-      NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
-      Source<WebContents>(this),
-      NotificationService::NoDetails());
+  NotificationService::current()->Notify(NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+                                         Source<WebContents>(this),
+                                         NotificationService::NoDetails());
 }
 
 void WebContentsImpl::UpdateTitle(RenderFrameHostImpl* render_frame_host,
@@ -7530,7 +7536,8 @@
 }
 
 void WebContentsImpl::BeforeUnloadFiredFromRenderManager(
-    bool proceed, const base::TimeTicks& proceed_time,
+    bool proceed,
+    const base::TimeTicks& proceed_time,
     bool* proceed_to_fire_unload) {
   OPTIONAL_TRACE_EVENT0("content",
                         "WebContentsImpl::BeforeUnloadFiredFromRenderManager");
@@ -7716,8 +7723,7 @@
   web_contents_android_.reset();
 }
 
-void WebContentsImpl::ActivateNearestFindResult(float x,
-                                                float y) {
+void WebContentsImpl::ActivateNearestFindResult(float x, float y) {
   OPTIONAL_TRACE_EVENT0("content",
                         "WebContentsImpl::ActivateNearestFindResult");
   GetOrCreateFindRequestManager()->ActivateNearestFindResult(x, y);
@@ -7783,8 +7789,8 @@
                                      bool dialog_was_suppressed,
                                      bool success,
                                      const std::u16string& user_input) {
-  RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(render_process_id,
-                                                         render_frame_id);
+  RenderFrameHostImpl* rfh =
+      RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
   OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::OnDialogClosed",
                         "render_frame_host", rfh);
   last_dialog_suppressed_ = dialog_was_suppressed;
@@ -7844,7 +7850,7 @@
 
 const std::vector<blink::mojom::FaviconURLPtr>&
 WebContentsImpl::GetFaviconURLs() {
-  return favicon_urls_;
+  return GetMainFrame()->FaviconURLs();
 }
 
 // The Mac implementation  of the next two methods is in
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 345cdec..2520b41 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -542,7 +542,7 @@
   bool CompletedFirstVisuallyNonEmptyPaint() override;
   void UpdateFaviconURL(
       RenderFrameHostImpl* source,
-      std::vector<blink::mojom::FaviconURLPtr> candidates) override;
+      const std::vector<blink::mojom::FaviconURLPtr>& candidates) override;
   const std::vector<blink::mojom::FaviconURLPtr>& GetFaviconURLs() override;
   void Resize(const gfx::Rect& new_bounds) override;
   gfx::Size GetSize() override;
@@ -2148,10 +2148,6 @@
   // successful navigation in this WebContents.
   bool first_navigation_completed_ = false;
 
-  // Represents the favicon urls candidates from the page.
-  // Empty std::vector until the first update from the renderer.
-  std::vector<blink::mojom::FaviconURLPtr> favicon_urls_;
-
   // Monitors system screen info changes to notify the renderer.
   std::unique_ptr<ScreenChangeMonitor> screen_change_monitor_;
 
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index 675e782..a49f57f 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -72,7 +72,6 @@
 #include "third_party/blink/public/common/chrome_debug_urls.h"
 #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
 #include "third_party/blink/public/common/security/protocol_handler_security_level.h"
-#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
 #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
 #include "third_party/blink/public/mojom/image_downloader/image_downloader.mojom.h"
 #include "third_party/blink/public/mojom/page/page_visibility_state.mojom.h"
@@ -2779,49 +2778,6 @@
   EXPECT_FALSE(contents()->IsConnectedToBluetoothDevice());
 }
 
-TEST_F(WebContentsImplTest, FaviconURLsSet) {
-  std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
-  const auto kFavicon =
-      blink::mojom::FaviconURL(GURL("https://example.com/favicon.ico"),
-                               blink::mojom::FaviconIconType::kFavicon, {});
-
-  contents()->NavigateAndCommit(GURL("https://example.com"));
-  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
-
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
-  contents()->UpdateFaviconURL(contents()->GetMainFrame(),
-                               std::move(favicon_urls));
-  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
-
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
-  contents()->UpdateFaviconURL(contents()->GetMainFrame(),
-                               std::move(favicon_urls));
-  EXPECT_EQ(2u, contents()->GetFaviconURLs().size());
-
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(kFavicon));
-  contents()->UpdateFaviconURL(contents()->GetMainFrame(),
-                               std::move(favicon_urls));
-  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
-}
-
-TEST_F(WebContentsImplTest, FaviconURLsResetWithNavigation) {
-  std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
-  favicon_urls.push_back(blink::mojom::FaviconURL::New(
-      GURL("https://example.com/favicon.ico"),
-      blink::mojom::FaviconIconType::kFavicon, std::vector<gfx::Size>()));
-
-  contents()->NavigateAndCommit(GURL("https://example.com"));
-  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
-
-  contents()->UpdateFaviconURL(contents()->GetMainFrame(),
-                               std::move(favicon_urls));
-  EXPECT_EQ(1u, contents()->GetFaviconURLs().size());
-
-  contents()->NavigateAndCommit(GURL("https://example.com/navigation.html"));
-  EXPECT_EQ(0u, contents()->GetFaviconURLs().size());
-}
-
 TEST_F(WebContentsImplTest, BadDownloadImageResponseFromRenderer) {
   // Avoid using TestWebContents, which fakes image download logic without
   // exercising the code in WebContentsImpl.
diff --git a/content/common/android/cpu_time_metrics.cc b/content/common/android/cpu_time_metrics.cc
index 9e49233..02fda64 100644
--- a/content/common/android/cpu_time_metrics.cc
+++ b/content/common/android/cpu_time_metrics.cc
@@ -113,6 +113,20 @@
   }
 }
 
+const char* GetPowerModeChangeHistogramNameForProcessType(
+    ProcessTypeForUma type) {
+  switch (type) {
+    case ProcessTypeForUma::kBrowser:
+      return "Power.PowerScheduler.ProcessPowerModeChange.Browser";
+    case ProcessTypeForUma::kRenderer:
+      return "Power.PowerScheduler.ProcessPowerModeChange.Renderer";
+    case ProcessTypeForUma::kGpu:
+      return "Power.PowerScheduler.ProcessPowerModeChange.GPU";
+    default:
+      return "Power.PowerScheduler.ProcessPowerModeChange.Other";
+  }
+}
+
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 // Keep in sync with power_scheduler::PowerMode.
@@ -127,7 +141,8 @@
   kCharging = 7,
   kNopAnimation = 8,
   kVideoPlayback = 9,
-  kMaxValue = kVideoPlayback,
+  kLoadingAnimation = 10,
+  kMaxValue = kLoadingAnimation,
 };
 
 PowerModeForUma GetPowerModeForUma(power_scheduler::PowerMode power_mode) {
@@ -144,6 +159,8 @@
       return PowerModeForUma::kLoading;
     case power_scheduler::PowerMode::kAnimation:
       return PowerModeForUma::kAnimation;
+    case power_scheduler::PowerMode::kLoadingAnimation:
+      return PowerModeForUma::kLoadingAnimation;
     case power_scheduler::PowerMode::kResponse:
       return PowerModeForUma::kResponse;
     case power_scheduler::PowerMode::kNonWebActivity:
@@ -432,6 +449,11 @@
     base::Optional<power_scheduler::PowerMode> old_power_mode =
         power_mode_.has_value() ? power_mode_ : old_mode;
     power_mode_ = new_mode;
+
+    UMA_HISTOGRAM_ENUMERATION(
+        GetPowerModeChangeHistogramNameForProcessType(process_type_),
+        GetPowerModeForUma(new_mode));
+
     if (collection_in_progress_.load(std::memory_order_relaxed))
       return;
 
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index a9f5711..ce48d78 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -105,6 +105,11 @@
   // constant over the navigation lifetime.
   virtual bool IsInPrimaryMainFrame() = 0;
 
+  // Prerender2
+  // Returns true if this navigation will activate a prerendered page. It is
+  // only meaningful to call this after BeginNavigation().
+  virtual bool IsPrerenderedPageActivation() = 0;
+
   // Whether the navigation was initiated by the renderer process. Examples of
   // renderer-initiated navigations include:
   //  * <a> link click
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 45f169c..bd9316bf 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -22,6 +22,7 @@
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-forward.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-forward.h"
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-forward.h"
+#include "third_party/blink/public/mojom/favicon/favicon_url.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/sudden_termination_disabler_type.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-forward.h"
@@ -69,7 +70,7 @@
 namespace net {
 class IsolationInfo;
 class NetworkIsolationKey;
-}
+}  // namespace net
 
 namespace network {
 namespace mojom {
@@ -854,10 +855,23 @@
   virtual void EnableWebRtcEventLogOutput(int lid, int output_period_ms) = 0;
   virtual void DisableWebRtcEventLogOutput(int lid) = 0;
 
+  // Return true if onload has been executed in the renderer in the main frame.
+  virtual bool IsDocumentOnLoadCompletedInMainFrame() = 0;
+
+  // The GURL for the document's web application manifest. If called on a
+  // subframe, returns the value from the corresponding main frame.  See
+  // https://w3c.github.io/manifest/#web-application-manifest
+  virtual const GURL& ManifestURL() = 0;
+
+  // Returns the raw list of favicon candidates as reported to observers via
+  // since the last navigation start. If called on a subframe, returns the
+  // value from the corresponding main frame.
+  virtual const std::vector<blink::mojom::FaviconURLPtr>& FaviconURLs() = 0;
+
  private:
   // This interface should only be implemented inside content.
   friend class RenderFrameHostImpl;
-  RenderFrameHost() {}
+  RenderFrameHost() = default;
 };
 
 }  // namespace content
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index d061fc18..c52791c 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -53,6 +53,7 @@
     return render_frame_host_ ? !render_frame_host_->GetParent() : true;
   }
   MOCK_METHOD0(IsInPrimaryMainFrame, bool());
+  MOCK_METHOD0(IsPrerenderedPageActivation, bool());
   // By default, MockNavigationHandles are renderer-initiated navigations.
   bool IsRendererInitiated() override { return is_renderer_initiated_; }
   bool IsSameOrigin() override {
diff --git a/content/public/test/prerender_test_util.cc b/content/public/test/prerender_test_util.cc
index c1f58de2..9f9e9c31 100644
--- a/content/public/test/prerender_test_util.cc
+++ b/content/public/test/prerender_test_util.cc
@@ -22,13 +22,16 @@
 namespace test {
 namespace {
 
-PrerenderHostRegistry& GetPrerenderHostRegistry(content::WebContents* tab) {
+PrerenderHostRegistry& GetPrerenderHostRegistry(
+    content::WebContents* web_contents) {
   EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
-  return *static_cast<WebContentsImpl*>(tab)->GetPrerenderHostRegistry();
+  return *static_cast<WebContentsImpl*>(web_contents)
+              ->GetPrerenderHostRegistry();
 }
 
-PrerenderHost* GetPrerenderHostById(content::WebContents* tab, int host_id) {
-  auto& registry = GetPrerenderHostRegistry(tab);
+PrerenderHost* GetPrerenderHostById(content::WebContents* web_contents,
+                                    int host_id) {
+  auto& registry = GetPrerenderHostRegistry(web_contents);
   return registry.FindNonReservedHostById(host_id);
 }
 
@@ -37,8 +40,9 @@
 class PrerenderHostRegistryObserverImpl
     : public PrerenderHostRegistry::Observer {
  public:
-  explicit PrerenderHostRegistryObserverImpl(content::WebContents* tab) {
-    observation_.Observe(&GetPrerenderHostRegistry(tab));
+  explicit PrerenderHostRegistryObserverImpl(
+      content::WebContents& web_contents) {
+    observation_.Observe(&GetPrerenderHostRegistry(&web_contents));
   }
 
   // Returns immediately if `url` was ever triggered before.
@@ -79,8 +83,9 @@
 };
 
 PrerenderHostRegistryObserver::PrerenderHostRegistryObserver(
-    content::WebContents* tab)
-    : impl_(std::make_unique<PrerenderHostRegistryObserverImpl>(tab)) {}
+    content::WebContents& web_contents)
+    : impl_(std::make_unique<PrerenderHostRegistryObserverImpl>(web_contents)) {
+}
 
 PrerenderHostRegistryObserver::~PrerenderHostRegistryObserver() = default;
 
@@ -90,8 +95,9 @@
 
 class PrerenderHostObserverImpl : public PrerenderHost::Observer {
  public:
-  explicit PrerenderHostObserverImpl(content::WebContents* tab, int host_id) {
-    observation_.Observe(GetPrerenderHostById(tab, host_id));
+  explicit PrerenderHostObserverImpl(content::WebContents& web_contents,
+                                     int host_id) {
+    observation_.Observe(GetPrerenderHostById(&web_contents, host_id));
   }
 
   void OnActivated() override {
@@ -133,9 +139,10 @@
   bool was_activated_ = false;
 };
 
-PrerenderHostObserver::PrerenderHostObserver(content::WebContents* tab,
+PrerenderHostObserver::PrerenderHostObserver(content::WebContents& web_contents,
                                              int prerender_host)
-    : impl_(std::make_unique<PrerenderHostObserverImpl>(tab, prerender_host)) {}
+    : impl_(std::make_unique<PrerenderHostObserverImpl>(web_contents,
+                                                        prerender_host)) {}
 
 PrerenderHostObserver::~PrerenderHostObserver() = default;
 
@@ -151,16 +158,11 @@
   return impl_->was_activated();
 }
 
-PrerenderTestHelper::PrerenderTestHelper(const content::WebContents::Getter& fn,
-                                         const std::string& prerendering_impl)
-    : get_web_contents_fn_(fn) {
-  std::map<std::string, std::string> parameters;
-  parameters["implementation"] = prerendering_impl;
-  feature_list_.InitAndEnableFeatureWithParameters(blink::features::kPrerender2,
-                                                   parameters);
-}
 PrerenderTestHelper::PrerenderTestHelper(const content::WebContents::Getter& fn)
-    : PrerenderTestHelper(fn, "mparch") {}
+    : get_web_contents_fn_(fn) {
+  feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
+}
+
 PrerenderTestHelper::~PrerenderTestHelper() = default;
 
 void PrerenderTestHelper::SetUpOnMainThread(
@@ -189,7 +191,7 @@
   PrerenderHost* host = registry.FindHostByUrlForTesting(gurl);
   // Wait for the host to be created if it hasn't yet.
   if (!host) {
-    PrerenderHostRegistryObserver observer(GetWebContents());
+    PrerenderHostRegistryObserver observer(*GetWebContents());
     observer.WaitForTrigger(gurl);
     host = registry.FindHostByUrlForTesting(gurl);
     ASSERT_NE(host, nullptr);
diff --git a/content/public/test/prerender_test_util.h b/content/public/test/prerender_test_util.h
index 789ef75..75bc35d4 100644
--- a/content/public/test/prerender_test_util.h
+++ b/content/public/test/prerender_test_util.h
@@ -24,7 +24,7 @@
 // for a given URL.
 class PrerenderHostRegistryObserver {
  public:
-  explicit PrerenderHostRegistryObserver(content::WebContents* tab);
+  explicit PrerenderHostRegistryObserver(content::WebContents& web_contents);
   ~PrerenderHostRegistryObserver();
   PrerenderHostRegistryObserver(const PrerenderHostRegistryObserver&) = delete;
   PrerenderHostRegistryObserver& operator=(
@@ -41,7 +41,7 @@
 // destruction
 class PrerenderHostObserver {
  public:
-  PrerenderHostObserver(content::WebContents* tab, int host_id);
+  PrerenderHostObserver(content::WebContents& web_contents, int host_id);
   ~PrerenderHostObserver();
   PrerenderHostObserver(const PrerenderHostObserver&) = delete;
   PrerenderHostObserver& operator=(const PrerenderHostObserver&) = delete;
@@ -53,13 +53,10 @@
   std::unique_ptr<PrerenderHostObserverImpl> impl_;
 };
 
-// Browser tests can aggregate this class to more conveniently leverage
-// prerendering.
+// Browser tests can use this class to more conveniently leverage prerendering.
 class PrerenderTestHelper {
  public:
   explicit PrerenderTestHelper(const content::WebContents::Getter& fn);
-  PrerenderTestHelper(const content::WebContents::Getter& fn,
-                      const std::string& prerendering_impl);
   ~PrerenderTestHelper();
   PrerenderTestHelper(const PrerenderTestHelper&) = delete;
   PrerenderTestHelper& operator=(const PrerenderTestHelper&) = delete;
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h
index 5f2b6d3..23fcd81 100644
--- a/content/public/test/test_renderer_host.h
+++ b/content/public/test/test_renderer_host.h
@@ -133,6 +133,10 @@
 
   // Get a count of the total number of heavy ad issues reported.
   virtual int GetHeavyAdIssueCount(HeavyAdIssueType type) = 0;
+
+  // Simulates the receipt of a manifest URL.
+  virtual void SimulateManifestURLUpdate(
+      const base::Optional<GURL>& manifest_url) = 0;
 };
 
 // An interface and utility for driving tests of RenderViewHost.
diff --git a/content/public/test/test_web_ui.cc b/content/public/test/test_web_ui.cc
index 0324e08..89497ba9 100644
--- a/content/public/test/test_web_ui.cc
+++ b/content/public/test/test_web_ui.cc
@@ -102,7 +102,7 @@
 void TestWebUI::CallJavascriptFunctionUnsafe(const std::string& function_name,
                                              const base::Value& arg1) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
-  call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
+  call_data_.back()->TakeAsArg1(base::Value::ToUniquePtrValue(arg1.Clone()));
   OnJavascriptCall(*call_data_.back());
 }
 
@@ -110,8 +110,8 @@
                                              const base::Value& arg1,
                                              const base::Value& arg2) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
-  call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
-  call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
+  call_data_.back()->TakeAsArg1(base::Value::ToUniquePtrValue(arg1.Clone()));
+  call_data_.back()->TakeAsArg2(base::Value::ToUniquePtrValue(arg2.Clone()));
   OnJavascriptCall(*call_data_.back());
 }
 
@@ -120,9 +120,9 @@
                                              const base::Value& arg2,
                                              const base::Value& arg3) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
-  call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
-  call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
-  call_data_.back()->TakeAsArg3(arg3.CreateDeepCopy());
+  call_data_.back()->TakeAsArg1(base::Value::ToUniquePtrValue(arg1.Clone()));
+  call_data_.back()->TakeAsArg2(base::Value::ToUniquePtrValue(arg2.Clone()));
+  call_data_.back()->TakeAsArg3(base::Value::ToUniquePtrValue(arg3.Clone()));
   OnJavascriptCall(*call_data_.back());
 }
 
@@ -132,10 +132,10 @@
                                              const base::Value& arg3,
                                              const base::Value& arg4) {
   call_data_.push_back(base::WrapUnique(new CallData(function_name)));
-  call_data_.back()->TakeAsArg1(arg1.CreateDeepCopy());
-  call_data_.back()->TakeAsArg2(arg2.CreateDeepCopy());
-  call_data_.back()->TakeAsArg3(arg3.CreateDeepCopy());
-  call_data_.back()->TakeAsArg4(arg4.CreateDeepCopy());
+  call_data_.back()->TakeAsArg1(base::Value::ToUniquePtrValue(arg1.Clone()));
+  call_data_.back()->TakeAsArg2(base::Value::ToUniquePtrValue(arg2.Clone()));
+  call_data_.back()->TakeAsArg3(base::Value::ToUniquePtrValue(arg3.Clone()));
+  call_data_.back()->TakeAsArg4(base::Value::ToUniquePtrValue(arg4.Clone()));
   OnJavascriptCall(*call_data_.back());
 }
 
diff --git a/content/renderer/compositor/compositor_dependencies.h b/content/renderer/compositor/compositor_dependencies.h
index 4ba08215..ca80115 100644
--- a/content/renderer/compositor/compositor_dependencies.h
+++ b/content/renderer/compositor/compositor_dependencies.h
@@ -17,15 +17,8 @@
 
 namespace cc {
 class TaskGraphRunner;
-class UkmRecorderFactory;
 }  // namespace cc
 
-namespace blink {
-namespace scheduler {
-class WebThreadScheduler;
-}
-}  // namespace blink
-
 namespace gfx {
 class RenderingPipeline;
 }  // namespace gfx
@@ -35,10 +28,7 @@
 class CONTENT_EXPORT CompositorDependencies {
  public:
   virtual bool IsUseZoomForDSFEnabled() = 0;
-  virtual blink::scheduler::WebThreadScheduler* GetWebMainThreadScheduler() = 0;
   virtual cc::TaskGraphRunner* GetTaskGraphRunner() = 0;
-  virtual std::unique_ptr<cc::UkmRecorderFactory>
-  CreateUkmRecorderFactory() = 0;
   virtual gfx::RenderingPipeline* GetMainThreadPipeline() = 0;
   virtual gfx::RenderingPipeline* GetCompositorThreadPipeline() = 0;
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index eaac22ff..d1f54fd 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1447,7 +1447,6 @@
       agent_scheduling_group.agent_group_scheduler(),
       compositor_deps->GetTaskGraphRunner(),
       params->widget_params->visual_properties.screen_infos,
-      compositor_deps->CreateUkmRecorderFactory(),
       /*settings=*/nullptr, compositor_deps->GetMainThreadPipeline(),
       compositor_deps->GetCompositorThreadPipeline());
 
@@ -1640,7 +1639,6 @@
         agent_scheduling_group.agent_group_scheduler(),
         compositor_deps->GetTaskGraphRunner(),
         widget_params->visual_properties.screen_infos,
-        compositor_deps->CreateUkmRecorderFactory(),
         /*settings=*/nullptr, compositor_deps->GetMainThreadPipeline(),
         compositor_deps->GetCompositorThreadPipeline());
 
@@ -1685,7 +1683,6 @@
         agent_scheduling_group.agent_group_scheduler(),
         compositor_deps->GetTaskGraphRunner(),
         widget_params->visual_properties.screen_infos,
-        compositor_deps->CreateUkmRecorderFactory(),
         /*settings=*/nullptr, compositor_deps->GetMainThreadPipeline(),
         compositor_deps->GetCompositorThreadPipeline());
 
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index fcb90d80..20bad6d2 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -350,25 +350,6 @@
   process_host->BindHostReceiver(std::move(receiver));
 }
 
-// This factory is used to defer binding of the InterfacePtr to the compositor
-// thread.
-class UkmRecorderFactoryImpl : public cc::UkmRecorderFactory {
- public:
-  explicit UkmRecorderFactoryImpl(
-      mojo::SharedRemote<mojom::ChildProcessHost> process_host)
-      : process_host_(std::move(process_host)) {}
-  ~UkmRecorderFactoryImpl() override = default;
-
-  std::unique_ptr<ukm::UkmRecorder> CreateRecorder() override {
-    mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
-    process_host_->BindHostReceiver(recorder.InitWithNewPipeAndPassReceiver());
-    return std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
-  }
-
- private:
-  const mojo::SharedRemote<mojom::ChildProcessHost> process_host_;
-};
-
 static bool IsSingleProcess() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       switches::kSingleProcess);
@@ -1304,11 +1285,6 @@
   is_scroll_animator_enabled_ = enable_scroll_animator;
 }
 
-std::unique_ptr<cc::UkmRecorderFactory>
-RenderThreadImpl::CreateUkmRecorderFactory() {
-  return std::make_unique<UkmRecorderFactoryImpl>(child_process_host());
-}
-
 gfx::RenderingPipeline* RenderThreadImpl::GetMainThreadPipeline() {
   return main_thread_pipeline_.get();
 }
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 31459a2..ec1f00f 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -202,12 +202,11 @@
 
   // CompositorDependencies implementation.
   bool IsUseZoomForDSFEnabled() override;
-  blink::scheduler::WebThreadScheduler* GetWebMainThreadScheduler() override;
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
-  std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
   gfx::RenderingPipeline* GetMainThreadPipeline() override;
   gfx::RenderingPipeline* GetCompositorThreadPipeline() override;
 
+  blink::scheduler::WebThreadScheduler* GetWebMainThreadScheduler();
   bool IsLcdTextEnabled();
   bool IsElasticOverscrollEnabled();
   bool IsScrollAnimatorEnabled();
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index b3dfc05..bb60e5d7 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -445,7 +445,6 @@
   popup->InitializeCompositing(agent_scheduling_group_.agent_group_scheduler(),
                                compositor_deps_->GetTaskGraphRunner(),
                                opener_widget->GetOriginalScreenInfos(),
-                               compositor_deps_->CreateUkmRecorderFactory(),
                                /*settings=*/nullptr,
                                compositor_deps_->GetMainThreadPipeline(),
                                compositor_deps_->GetCompositorThreadPipeline());
diff --git a/content/services/auction_worklet/auction_runner.cc b/content/services/auction_worklet/auction_runner.cc
index 2ae64374..da5d9123 100644
--- a/content/services/auction_worklet/auction_runner.cc
+++ b/content/services/auction_worklet/auction_runner.cc
@@ -60,7 +60,10 @@
     bid_state->bidder = bidder.get();
     // TODO(morlovich): Straight skip if URL is missing.
     bid_state->bidder_worklet = std::make_unique<BidderWorklet>(
-        url_loader_factory_.get(), bidder->group->bidding_url.value_or(GURL()),
+        url_loader_factory_.get(), bidder->Clone(),
+        auction_config_->auction_signals, PerBuyerSignals(bid_state),
+        browser_signals_->top_frame_origin,
+        browser_signals_->seller.Serialize(), auction_start_time_,
         &auction_v8_helper_,
         base::BindOnce(&AuctionRunner::OnBidderScriptLoaded,
                        base::Unretained(this), bid_state));
@@ -128,16 +131,8 @@
 
 void AuctionRunner::RunBid(BidState* state) {
   base::TimeTicks start = base::TimeTicks::Now();
-  state->bid_result = state->bidder_worklet->GenerateBid(
-      *state->bidder->group, auction_config_->auction_signals,
-      PerBuyerSignals(state),
-      state->bidder->group->trusted_bidding_signals_keys.value_or(
-          std::vector<std::string>()),
-      state->trusted_bidding_signals.get(),
-      browser_signals_->top_frame_origin.host(),
-      browser_signals_->seller.Serialize(), state->bidder->signals->join_count,
-      state->bidder->signals->bid_count, state->bidder->signals->prev_wins,
-      auction_start_time_);
+  state->bid_result =
+      state->bidder_worklet->GenerateBid(state->trusted_bidding_signals.get());
   state->bid_duration = base::TimeTicks::Now() - start;
 }
 
@@ -241,10 +236,7 @@
     const BidState* best_bid,
     const SellerWorklet::Report& seller_report) {
   return best_bid->bidder_worklet->ReportWin(
-      auction_config_->auction_signals, PerBuyerSignals(best_bid),
-      seller_report.signals_for_winner,
-      browser_signals_->top_frame_origin.host(), best_bid->bidder->group->owner,
-      best_bid->bidder->group->name, best_bid->bid_result.render_url,
+      seller_report.signals_for_winner, best_bid->bid_result.render_url,
       AdRenderFingerprint(best_bid), best_bid->bid_result.bid);
 }
 
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index dead3aee..c2f73b21 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -68,13 +68,28 @@
 
 BidderWorklet::BidderWorklet(
     network::mojom::URLLoaderFactory* url_loader_factory,
-    const GURL& script_source_url,
+    mojom::BiddingInterestGroupPtr bidding_interest_group,
+    const base::Optional<std::string>& auction_signals_json,
+    const base::Optional<std::string>& per_buyer_signals_json,
+    const url::Origin& browser_signal_top_window_origin,
+    const std::string& browser_signal_seller,
+    base::Time auction_start_time,
     AuctionV8Helper* v8_helper,
     LoadWorkletCallback load_worklet_callback)
-    : v8_helper_(v8_helper) {
+    : v8_helper_(v8_helper),
+      bidding_interest_group_(std::move(bidding_interest_group)),
+      auction_signals_json_(auction_signals_json),
+      per_buyer_signals_json_(per_buyer_signals_json),
+      browser_signal_top_window_hostname_(
+          browser_signal_top_window_origin.host()),
+      browser_signal_seller_(browser_signal_seller),
+      auction_start_time_(auction_start_time) {
   DCHECK(load_worklet_callback);
+  // TODO(mmenke): Remove up the value_or() - auction worklets shouldn't be
+  // created when there's no bidding URL.
   worklet_loader_ = std::make_unique<WorkletLoader>(
-      url_loader_factory, script_source_url, v8_helper,
+      url_loader_factory,
+      bidding_interest_group_->group->bidding_url.value_or(GURL()), v8_helper,
       base::BindOnce(&BidderWorklet::OnDownloadComplete, base::Unretained(this),
                      std::move(load_worklet_callback)));
 }
@@ -82,18 +97,9 @@
 BidderWorklet::~BidderWorklet() = default;
 
 BidderWorklet::BidResult BidderWorklet::GenerateBid(
-    const blink::mojom::InterestGroup& interest_group,
-    const base::Optional<std::string>& auction_signals_json,
-    const base::Optional<std::string>& per_buyer_signals_json,
-    const std::vector<std::string>& trusted_bidding_signals_keys,
-    TrustedBiddingSignals* trusted_bidding_signals,
-    const std::string& browser_signal_top_window_hostname,
-    const std::string& browser_signal_seller,
-    int browser_signal_join_count,
-    int browser_signal_bid_count,
-    const std::vector<mojo::StructPtr<mojom::PreviousWin>>&
-        browser_signal_prev_wins,
-    base::Time auction_start_time) {
+    TrustedBiddingSignals* trusted_bidding_signals) {
+  const blink::mojom::InterestGroup& interest_group =
+      *bidding_interest_group_->group;
   // Can't make a bid without any ads.
   if (!interest_group.ads)
     return BidResult();
@@ -140,35 +146,39 @@
 
   args.push_back(std::move(interest_group_object));
 
-  if (!AppendJsonValueOrNull(v8_helper_, context, auction_signals_json,
+  if (!AppendJsonValueOrNull(v8_helper_, context, auction_signals_json_,
                              &args) ||
-      !AppendJsonValueOrNull(v8_helper_, context, per_buyer_signals_json,
+      !AppendJsonValueOrNull(v8_helper_, context, per_buyer_signals_json_,
                              &args)) {
     return BidResult();
   }
 
   v8::Local<v8::Value> trusted_signals;
-  if (!trusted_bidding_signals || trusted_bidding_signals_keys.empty()) {
+  if (!trusted_bidding_signals ||
+      !interest_group.trusted_bidding_signals_keys ||
+      interest_group.trusted_bidding_signals_keys->empty()) {
     trusted_signals = v8::Null(isolate);
   } else {
     trusted_signals = trusted_bidding_signals->GetSignals(
-        context, trusted_bidding_signals_keys);
+        context, *interest_group.trusted_bidding_signals_keys);
   }
   args.push_back(trusted_signals);
 
   v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
   gin::Dictionary browser_signals_dict(isolate, browser_signals);
   if (!browser_signals_dict.Set("topWindowHostname",
-                                browser_signal_top_window_hostname) ||
-      !browser_signals_dict.Set("seller", browser_signal_seller) ||
-      !browser_signals_dict.Set("joinCount", browser_signal_join_count) ||
-      !browser_signals_dict.Set("bidCount", browser_signal_bid_count)) {
+                                browser_signal_top_window_hostname_) ||
+      !browser_signals_dict.Set("seller", browser_signal_seller_) ||
+      !browser_signals_dict.Set("joinCount",
+                                bidding_interest_group_->signals->join_count) ||
+      !browser_signals_dict.Set("bidCount",
+                                bidding_interest_group_->signals->bid_count)) {
     return BidResult();
   }
 
   std::vector<v8::Local<v8::Value>> prev_wins_v8;
-  for (const auto& prev_win : browser_signal_prev_wins) {
-    int64_t time_delta = (auction_start_time - prev_win->time).InSeconds();
+  for (const auto& prev_win : bidding_interest_group_->signals->prev_wins) {
+    int64_t time_delta = (auction_start_time_ - prev_win->time).InSeconds();
     // Don't give negative times if clock has changed since last auction win.
     // Clock changes do mean times can be out of numerical order, despite being
     // in chronological order.
@@ -230,12 +240,7 @@
 }
 
 BidderWorklet::ReportWinResult BidderWorklet::ReportWin(
-    const base::Optional<std::string>& auction_signals_json,
-    const base::Optional<std::string>& per_buyer_signals_json,
     const std::string& seller_signals_json,
-    const std::string& browser_signal_top_window_hostname,
-    const url::Origin& browser_signal_interest_group_owner,
-    const std::string& browser_signal_interest_group_name,
     const GURL& browser_signal_render_url,
     const std::string& browser_signal_ad_render_fingerprint,
     double browser_signal_bid) {
@@ -252,9 +257,9 @@
   v8::Context::Scope context_scope(context);
 
   std::vector<v8::Local<v8::Value>> args;
-  if (!AppendJsonValueOrNull(v8_helper_, context, auction_signals_json,
+  if (!AppendJsonValueOrNull(v8_helper_, context, auction_signals_json_,
                              &args) ||
-      !AppendJsonValueOrNull(v8_helper_, context, per_buyer_signals_json,
+      !AppendJsonValueOrNull(v8_helper_, context, per_buyer_signals_json_,
                              &args) ||
       !v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) {
     return ReportWinResult();
@@ -263,12 +268,12 @@
   v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
   gin::Dictionary browser_signals_dict(isolate, browser_signals);
   if (!browser_signals_dict.Set("topWindowHostname",
-                                browser_signal_top_window_hostname) ||
+                                browser_signal_top_window_hostname_) ||
       !browser_signals_dict.Set(
           "interestGroupOwner",
-          browser_signal_interest_group_owner.Serialize()) ||
+          bidding_interest_group_->group->owner.Serialize()) ||
       !browser_signals_dict.Set("interestGroupName",
-                                browser_signal_interest_group_name) ||
+                                bidding_interest_group_->group->name) ||
       !browser_signals_dict.Set("renderUrl",
                                 browser_signal_render_url.spec()) ||
       !browser_signals_dict.Set("adRenderFingerprint",
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h
index 5a4db4f..c1d3a9f 100644
--- a/content/services/auction_worklet/bidder_worklet.h
+++ b/content/services/auction_worklet/bidder_worklet.h
@@ -13,6 +13,7 @@
 #include "base/callback.h"
 #include "base/time/time.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
+#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "mojo/public/cpp/bindings/struct_ptr.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
 #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-forward.h"
@@ -29,6 +30,14 @@
 // Represents a bidder worklet for FLEDGE
 // (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Loads and runs the
 // bidder worklet's Javascript.
+//
+// Each worklet object can only be used to load and run a single script's
+// generateBid() and (if the bid is won) reportWin() once.
+//
+// TODO(mmenke): Make worklets reuseable. Allow a single BidderWorklet instance
+// to both be used for two generateBid() calls for different interest groups
+// with the same owner in the same auction, and to be used to bid for the same
+// interest group in different auctions.
 class BidderWorklet {
  public:
   struct BidResult {
@@ -83,11 +92,16 @@
 
   // Starts loading the worklet script on construction. Callback will be invoked
   // asynchronously once the data has been fetched or an error has occurred.
-  // Must be destroyed before `v8_helper`. No data is leaked between consecutive
-  // invocations of this method, or between invocations of this method and
-  // GenerateBid().
+  // Must be destroyed before `v8_helper`.
+  //
+  // Data is cached and reused in GenerateBid() and ReportWin().
   BidderWorklet(network::mojom::URLLoaderFactory* url_loader_factory,
-                const GURL& script_source_url,
+                mojom::BiddingInterestGroupPtr bidding_interest_group,
+                const base::Optional<std::string>& auction_signals_json,
+                const base::Optional<std::string>& per_buyer_signals_json,
+                const url::Origin& browser_signal_top_window_origin,
+                const std::string& browser_signal_seller,
+                base::Time auction_start_time,
                 AuctionV8Helper* v8_helper,
                 LoadWorkletCallback load_worklet_callback);
   explicit BidderWorklet(const BidderWorklet&) = delete;
@@ -96,29 +110,12 @@
 
   // Calls generateBid(), and returns resulting bid, if any. May only be called
   // once BidderWorklet has successfully loaded.
-  BidResult GenerateBid(
-      const blink::mojom::InterestGroup& interest_group,
-      const base::Optional<std::string>& auction_signals_json,
-      const base::Optional<std::string>& per_buyer_signals_json,
-      const std::vector<std::string>& trusted_bidding_signals_keys,
-      TrustedBiddingSignals* trusted_bidding_signals,
-      const std::string& browser_signal_top_window_hostname,
-      const std::string& browser_signal_seller,
-      int browser_signal_join_count,
-      int browser_signal_bid_count,
-      const std::vector<mojo::StructPtr<mojom::PreviousWin>>&
-          browser_signal_prev_wins,
-      base::Time auction_start_time);
+  BidResult GenerateBid(TrustedBiddingSignals* trusted_bidding_signals);
 
   // Calls reportWin(), and returns reporting information. May only be called
   // once the worklet has successfully loaded.
   ReportWinResult ReportWin(
-      const base::Optional<std::string>& auction_signals_json,
-      const base::Optional<std::string>& per_buyer_signals_json,
       const std::string& seller_signals_json,
-      const std::string& browser_signal_top_window_hostname,
-      const url::Origin& browser_signal_interest_group_owner,
-      const std::string& browser_signal_interest_group_name,
       const GURL& browser_signal_render_url,
       const std::string& browser_signal_ad_render_fingerprint,
       double browser_signal_bid);
@@ -129,6 +126,14 @@
       std::unique_ptr<v8::Global<v8::UnboundScript>> worklet_script);
 
   AuctionV8Helper* const v8_helper_;
+  const mojom::BiddingInterestGroupPtr bidding_interest_group_;
+
+  const base::Optional<std::string> auction_signals_json_;
+  const base::Optional<std::string> per_buyer_signals_json_;
+  const std::string browser_signal_top_window_hostname_;
+  const std::string browser_signal_seller_;
+  const base::Time auction_start_time_;
+
   std::unique_ptr<WorkletLoader> worklet_loader_;
 
   // Compiled script, not bound to any context. Can be repeatedly bound to
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 3e5811d..c18d623 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -75,15 +75,17 @@
     interest_group_ads_.clear();
     interest_group_ads_.push_back(blink::mojom::InterestGroupAd::New(
         GURL("https://response.test/"), base::nullopt /* metadata */));
+    browser_signal_join_count_ = 2;
+    browser_signal_bid_count_ = 3;
+    browser_signal_prev_wins_.clear();
+
     auction_signals_ = "[\"auction_signals\"]";
     null_auction_signals_ = false;
     per_buyer_signals_ = "[\"per_buyer_signals\"]";
     null_per_buyer_signals_ = false;
-    browser_signal_top_window_hostname_ = "browser_signal_top_window_hostname";
+    browser_signal_top_window_origin_ =
+        url::Origin::Create(GURL("https://top.window.test/"));
     browser_signal_seller_ = "browser_signal_seller";
-    browser_signal_join_count_ = 2;
-    browser_signal_bid_count_ = 3;
-    browser_signal_prev_wins_.clear();
     seller_signals_ = "[\"seller_signals\"]";
     browser_signal_render_url_ = GURL("https://render_url.test/");
     browser_signal_ad_render_fingerprint_ =
@@ -106,7 +108,8 @@
       const std::string& javascript,
       const BidderWorklet::BidResult& expected_result) {
     SCOPED_TRACE(javascript);
-    AddJavascriptResponse(&url_loader_factory_, url_, javascript);
+    AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
+                          javascript);
     RunGenerateBidExpectingResult(expected_result);
   }
 
@@ -123,31 +126,7 @@
   }
 
   BidderWorklet::BidResult RunGenerateBid(BidderWorklet* bidder_worket) {
-    blink::mojom::InterestGroupPtr interest_group =
-        blink::mojom::InterestGroup::New();
-    interest_group->owner = interest_group_owner_;
-    interest_group->name = interest_group_name_;
-    // Convert a string to an optional. Empty string means empty optional value.
-    if (!interest_group_user_bidding_signals_.empty()) {
-      interest_group->user_bidding_signals =
-          interest_group_user_bidding_signals_;
-    }
-    interest_group->ads = std::vector<blink::mojom::InterestGroupAdPtr>();
-    for (const auto& ad : interest_group_ads_) {
-      interest_group->ads->emplace_back(ad.Clone());
-    }
-    return bidder_worket->GenerateBid(
-        *interest_group,
-        null_auction_signals_
-            ? base::nullopt
-            : base::make_optional<std::string>(auction_signals_),
-        null_per_buyer_signals_
-            ? base::nullopt
-            : base::make_optional<std::string>(per_buyer_signals_),
-        trusted_bidding_signals_keys_, trusted_bidding_signals_.get(),
-        browser_signal_top_window_hostname_, browser_signal_seller_,
-        browser_signal_join_count_, browser_signal_bid_count_,
-        browser_signal_prev_wins_, auction_start_time_);
+    return bidder_worket->GenerateBid(trusted_bidding_signals_.get());
   }
 
   void ExpectBidResultsEqual(const BidderWorklet::BidResult& expected_result,
@@ -173,7 +152,8 @@
       const std::string& javascript,
       const GURL& expected_report_url) {
     SCOPED_TRACE(javascript);
-    AddJavascriptResponse(&url_loader_factory_, url_, javascript);
+    AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
+                          javascript);
     RunReportWinExpectingResult(expected_report_url);
   }
 
@@ -184,14 +164,7 @@
     ASSERT_TRUE(bidder_worket);
 
     BidderWorklet::ReportWinResult actual_result = bidder_worket->ReportWin(
-        null_auction_signals_
-            ? base::nullopt
-            : base::make_optional<std::string>(auction_signals_),
-        null_per_buyer_signals_
-            ? base::nullopt
-            : base::make_optional<std::string>(per_buyer_signals_),
-        seller_signals_, browser_signal_top_window_hostname_,
-        interest_group_owner_, interest_group_name_, browser_signal_render_url_,
+        seller_signals_, browser_signal_render_url_,
         browser_signal_ad_render_fingerprint_, browser_signal_bid_);
     EXPECT_EQ(!expected_report_url.is_empty(), actual_result.success);
     EXPECT_EQ(expected_report_url, actual_result.report_url);
@@ -202,9 +175,42 @@
   std::unique_ptr<BidderWorklet> CreateWorklet() {
     CHECK(!load_script_run_loop_);
 
+    blink::mojom::InterestGroupPtr interest_group =
+        blink::mojom::InterestGroup::New();
+    interest_group->owner = interest_group_owner_;
+    interest_group->name = interest_group_name_;
+    interest_group->bidding_url = interest_group_bidding_url_;
+    // Convert a string to an optional. Empty string means empty optional value.
+    if (!interest_group_user_bidding_signals_.empty()) {
+      interest_group->user_bidding_signals =
+          interest_group_user_bidding_signals_;
+    }
+    interest_group->trusted_bidding_signals_keys =
+        interest_group_trusted_bidding_signals_keys_;
+    interest_group->ads = std::vector<blink::mojom::InterestGroupAdPtr>();
+    for (const auto& ad : interest_group_ads_) {
+      interest_group->ads->emplace_back(ad.Clone());
+    }
+
+    mojom::BiddingBrowserSignalsPtr bidding_browser_signals =
+        mojom::BiddingBrowserSignals::New(
+            browser_signal_join_count_, browser_signal_bid_count_,
+            CloneWinList(browser_signal_prev_wins_));
+    mojom::BiddingInterestGroupPtr bidding_interest_group =
+        mojom::BiddingInterestGroup::New(std::move(interest_group),
+                                         std::move(bidding_browser_signals));
+
     create_worklet_succeeded_ = false;
     auto bidder_worket = std::make_unique<BidderWorklet>(
-        &url_loader_factory_, url_, &v8_helper_,
+        &url_loader_factory_, std::move(bidding_interest_group),
+        null_auction_signals_
+            ? base::nullopt
+            : base::make_optional<std::string>(auction_signals_),
+        null_per_buyer_signals_
+            ? base::nullopt
+            : base::make_optional<std::string>(per_buyer_signals_),
+        browser_signal_top_window_origin_, browser_signal_seller_,
+        auction_start_time_, &v8_helper_,
         base::BindOnce(&BidderWorkletTest::CreateWorkletCallback,
                        base::Unretained(this)));
     load_script_run_loop_ = std::make_unique<base::RunLoop>();
@@ -216,6 +222,15 @@
   }
 
  protected:
+  std::vector<mojo::StructPtr<mojom::PreviousWin>> CloneWinList(
+      const std::vector<mojo::StructPtr<mojom::PreviousWin>>& prev_win_list) {
+    std::vector<mojo::StructPtr<mojom::PreviousWin>> out;
+    for (const auto& prev_win : prev_win_list) {
+      out.push_back(prev_win->Clone());
+    }
+    return out;
+  }
+
   void CreateWorkletCallback(bool success) {
     create_worklet_succeeded_ = success;
     load_script_run_loop_->Quit();
@@ -223,17 +238,19 @@
 
   base::test::TaskEnvironment task_environment_;
 
-  const GURL url_ = GURL("https://url.test/");
-
-  // Arguments passed to generateBid() and reportWin(). Arguments that both
-  // methods take are shared, as are interest group fields that also appear in
-  // `browserSignals`.
+  // Values used to construct the BiddingInterestGroup passed to the
+  // BidderWorklet.
   url::Origin interest_group_owner_;
   std::string interest_group_name_;
-  // This is actually an option value, but to make testing easier, use a string.
-  // An empty string means nullptr.
+  const GURL interest_group_bidding_url_ = GURL("https://url.test/");
+  // This is actually an optional value, but to make testing easier, use a
+  // string. An empty string means nullptr.
   std::string interest_group_user_bidding_signals_;
   std::vector<blink::mojom::InterestGroupAdPtr> interest_group_ads_;
+  std::vector<std::string> interest_group_trusted_bidding_signals_keys_;
+  int browser_signal_join_count_;
+  int browser_signal_bid_count_;
+  std::vector<mojo::StructPtr<mojom::PreviousWin>> browser_signal_prev_wins_;
 
   std::string auction_signals_;
   // true to pass nullopt rather than `auction_signals_`.
@@ -243,12 +260,8 @@
   // true to pass nullopt rather than `per_buyer_signals_`.
   bool null_per_buyer_signals_ = false;
 
-  std::string browser_signal_top_window_hostname_;
+  url::Origin browser_signal_top_window_origin_;
   std::string browser_signal_seller_;
-  int browser_signal_join_count_;
-  int browser_signal_bid_count_;
-  std::vector<mojo::StructPtr<mojom::PreviousWin>> browser_signal_prev_wins_;
-  std::vector<std::string> trusted_bidding_signals_keys_;
   std::unique_ptr<TrustedBiddingSignals> trusted_bidding_signals_;
   std::string seller_signals_;
   GURL browser_signal_render_url_;
@@ -270,13 +283,15 @@
 };
 
 TEST_F(BidderWorkletTest, NetworkError) {
-  url_loader_factory_.AddResponse(url_.spec(), CreateBasicGenerateBidScript(),
+  url_loader_factory_.AddResponse(interest_group_bidding_url_.spec(),
+                                  CreateBasicGenerateBidScript(),
                                   net::HTTP_NOT_FOUND);
   EXPECT_FALSE(CreateWorklet());
 }
 
 TEST_F(BidderWorkletTest, CompileError) {
-  AddJavascriptResponse(&url_loader_factory_, url_, "Invalid Javascript");
+  AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
+                        "Invalid Javascript");
   EXPECT_FALSE(CreateWorklet());
 }
 
@@ -489,11 +504,6 @@
           &per_buyer_signals_,
       },
       {
-          "browserSignals.topWindowHostname",
-          false /* is_json */,
-          &browser_signal_top_window_hostname_,
-      },
-      {
           "browserSignals.seller",
           false /* is_json */,
           &browser_signal_seller_,
@@ -559,6 +569,14 @@
                                GURL("https://response.test/")));
   SetDefaultParameters();
 
+  browser_signal_top_window_origin_ =
+      url::Origin::Create(GURL("https://top.window.test/"));
+  RunGenerateBidWithReturnValueExpectingResult(
+      R"({ad: browserSignals.topWindowHostname, bid:1, render:"https://response.test/"})",
+      BidderWorklet::BidResult(R"("top.window.test")", 1,
+                               GURL("https://response.test/")));
+  SetDefaultParameters();
+
   const struct IntegerTestCase {
     // String used in JS to access the parameter.
     const char* name;
@@ -780,18 +798,18 @@
       R"({ad: trustedBiddingSignals, bid:1, render:"https://response.test/"})",
       BidderWorklet::BidResult("null", 1, GURL("https://response.test/")));
 
-  trusted_bidding_signals_keys_ = {"key1"};
+  interest_group_trusted_bidding_signals_keys_ = {"key1"};
   RunGenerateBidWithReturnValueExpectingResult(
       R"({ad: trustedBiddingSignals["key1"], bid:1, render:"https://response.test/"})",
       BidderWorklet::BidResult("1", 1, GURL("https://response.test/")));
 
-  trusted_bidding_signals_keys_ = {"key2"};
+  interest_group_trusted_bidding_signals_keys_ = {"key2"};
   RunGenerateBidWithReturnValueExpectingResult(
       R"({ad: trustedBiddingSignals, bid:1, render:"https://response.test/"})",
       BidderWorklet::BidResult(R"({"key2":[2]})", 1,
                                GURL("https://response.test/")));
 
-  trusted_bidding_signals_keys_ = {"key1", "key2"};
+  interest_group_trusted_bidding_signals_keys_ = {"key1", "key2"};
   RunGenerateBidWithReturnValueExpectingResult(
       R"({ad: trustedBiddingSignals, bid:1, render:"https://response.test/"})",
       BidderWorklet::BidResult(R"({"key1":1,"key2":[2]})", 1,
@@ -851,11 +869,6 @@
           &seller_signals_,
       },
       {
-          "browserSignals.topWindowHostname",
-          false /* is_json */,
-          &browser_signal_top_window_hostname_,
-      },
-      {
           "browserSignals.interestGroupName",
           false /* is_json */,
           &interest_group_name_,
@@ -897,6 +910,13 @@
       GURL("https://[::1]:40000/"));
   SetDefaultParameters();
 
+  browser_signal_top_window_origin_ =
+      url::Origin::Create(GURL("https://top.window.test/"));
+  RunReportWinWithFunctionBodyExpectingResult(
+      R"(sendReportTo("https://" + browserSignals.topWindowHostname))",
+      GURL("https://top.window.test/"));
+  SetDefaultParameters();
+
   browser_signal_render_url_ = GURL("https://shrimp.test/");
   RunReportWinWithFunctionBodyExpectingResult(
       "sendReportTo(browserSignals.renderUrl)", browser_signal_render_url_);
@@ -949,7 +969,7 @@
   // Use arrays so that all values are references, to catch both the case where
   // variables are persisted, and the case where what they refer to is
   // persisted, but variables are overwritten between runs.
-  AddJavascriptResponse(&url_loader_factory_, url_,
+  AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
                         R"(
         // Globally scoped variable.
         if (!globalThis.var1)
diff --git a/content/test/data/conversions/databases/version_4.sql b/content/test/data/conversions/databases/version_4.sql
index ba8e0b1..faf7cd3b 100644
--- a/content/test/data/conversions/databases/version_4.sql
+++ b/content/test/data/conversions/databases/version_4.sql
@@ -11,7 +11,7 @@
 CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
 
 INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','3');
+INSERT INTO meta VALUES('version','4');
 INSERT INTO meta VALUES('last_compatible_version','3');
 
 CREATE INDEX conversion_destination_idx ON impressions(active, conversion_destination, reporting_origin);
@@ -30,4 +30,10 @@
 
 CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
 
+-- Add some conversions to test 0-credit deletion in version 5.
+INSERT INTO conversions (conversion_data, conversion_time, report_time, attribution_credit) VALUES
+  ("a", 2, 3, 5),
+  ("b", 7, 11, 0),
+  ("c", 13, 17, 19);
+
 COMMIT;
diff --git a/content/test/data/conversions/databases/version_5.sql b/content/test/data/conversions/databases/version_5.sql
new file mode 100644
index 0000000..da8bea2
--- /dev/null
+++ b/content/test/data/conversions/databases/version_5.sql
@@ -0,0 +1,33 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER DEFAULT 0,active INTEGER DEFAULT 1,conversion_destination TEXT NOT NULL,source_type INTEGER NOT NULL,attributed_truthfully INTEGER NOT NULL);
+
+CREATE TABLE conversions (conversion_id INTEGER PRIMARY KEY, impression_id INTEGER, conversion_data TEXT NOT NULL, conversion_time INTEGER NOT NULL, report_time INTEGER NOT NULL);
+
+CREATE TABLE rate_limits(rate_limit_id INTEGER PRIMARY KEY,attribution_type INTEGER NOT NULL,impression_id INTEGER NOT NULL,impression_site TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_destination TEXT NOT NULL,conversion_origin TEXT NOT NULL,conversion_time INTEGER NOT NULL);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('mmap_status','-1');
+INSERT INTO meta VALUES('version','5');
+INSERT INTO meta VALUES('last_compatible_version','5');
+
+CREATE INDEX conversion_destination_idx ON impressions(active, conversion_destination, reporting_origin);
+
+CREATE INDEX impression_expiry_idx ON impressions(expiry_time);
+
+CREATE INDEX impression_origin_idx ON impressions(impression_origin);
+
+CREATE INDEX conversion_report_idx ON conversions(report_time);
+
+CREATE INDEX conversion_impression_id_idx ON conversions(impression_id);
+
+CREATE INDEX rate_limit_impression_site_type_idx ON rate_limits(attribution_type, conversion_destination, impression_site, conversion_time);
+
+CREATE INDEX rate_limit_conversion_time_idx ON rate_limits(conversion_time);
+
+CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
+
+COMMIT;
diff --git a/content/test/fake_compositor_dependencies.cc b/content/test/fake_compositor_dependencies.cc
index a06c35d0..bd705ed 100644
--- a/content/test/fake_compositor_dependencies.cc
+++ b/content/test/fake_compositor_dependencies.cc
@@ -26,20 +26,10 @@
   return use_zoom_for_dsf_;
 }
 
-blink::scheduler::WebThreadScheduler*
-FakeCompositorDependencies::GetWebMainThreadScheduler() {
-  return &main_thread_scheduler_;
-}
-
 cc::TaskGraphRunner* FakeCompositorDependencies::GetTaskGraphRunner() {
   return &task_graph_runner_;
 }
 
-std::unique_ptr<cc::UkmRecorderFactory>
-FakeCompositorDependencies::CreateUkmRecorderFactory() {
-  return std::make_unique<cc::TestUkmRecorderFactory>();
-}
-
 gfx::RenderingPipeline* FakeCompositorDependencies::GetMainThreadPipeline() {
   // TODO(crbug.com/1157620): Implement to test rendering pipelines.
   return nullptr;
diff --git a/content/test/fake_compositor_dependencies.h b/content/test/fake_compositor_dependencies.h
index 02fe885..2d62307 100644
--- a/content/test/fake_compositor_dependencies.h
+++ b/content/test/fake_compositor_dependencies.h
@@ -11,7 +11,6 @@
 #include "content/renderer/compositor/compositor_dependencies.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/blink/public/platform/scheduler/test/web_fake_thread_scheduler.h"
 
 namespace content {
 
@@ -22,9 +21,7 @@
 
   // CompositorDependencies implementation.
   bool IsUseZoomForDSFEnabled() override;
-  blink::scheduler::WebThreadScheduler* GetWebMainThreadScheduler() override;
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
-  std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
   gfx::RenderingPipeline* GetMainThreadPipeline() override;
   gfx::RenderingPipeline* GetCompositorThreadPipeline() override;
 
@@ -34,7 +31,6 @@
 
  private:
   cc::TestTaskGraphRunner task_graph_runner_;
-  blink::scheduler::WebFakeThreadScheduler main_thread_scheduler_;
   bool use_zoom_for_dsf_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(FakeCompositorDependencies);
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index 44a5081c..e42a5b0 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -223,6 +223,11 @@
   }
 }
 
+void TestRenderFrameHost::SimulateManifestURLUpdate(
+    const base::Optional<GURL>& manifest_url) {
+  UpdateManifestURL(manifest_url);
+}
+
 void TestRenderFrameHost::SendNavigate(int nav_entry_id,
                                        bool did_create_new_entry,
                                        const GURL& url) {
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h
index c78119f..ec04227 100644
--- a/content/test/test_render_frame_host.h
+++ b/content/test/test_render_frame_host.h
@@ -93,6 +93,8 @@
   void SimulateUserActivation() override;
   const std::vector<std::string>& GetConsoleMessages() override;
   int GetHeavyAdIssueCount(HeavyAdIssueType type) override;
+  void SimulateManifestURLUpdate(
+      const base::Optional<GURL>& manifest_url) override;
 
   void SendNavigate(int nav_entry_id,
                     bool did_create_new_entry,
diff --git a/content/web_test/browser/web_test_browser_main_runner.cc b/content/web_test/browser/web_test_browser_main_runner.cc
index 0ade439..b9eb60b 100644
--- a/content/web_test/browser/web_test_browser_main_runner.cc
+++ b/content/web_test/browser/web_test_browser_main_runner.cc
@@ -148,7 +148,8 @@
   if (!command_line.HasSwitch(switches::kUseGpuInTests) &&
       !command_line.HasSwitch(switches::kUseGL)) {
     bool legacy_software_gl = true;
-#if (defined(OS_LINUX) && !defined(OS_CHROMEOS) && !defined(OS_FUCHSIA))
+#if defined(OS_LINUX) || defined(OS_WIN)
+    // This setting makes web tests run on SwANGLE instead of SwiftShader GL.
     legacy_software_gl = false;
 #endif
     gl::SetSoftwareGLCommandLineSwitches(&command_line, legacy_software_gl);
diff --git a/device/bluetooth/bluetooth_local_gatt_descriptor.h b/device/bluetooth/bluetooth_local_gatt_descriptor.h
index 6565764..a6cd5c7 100644
--- a/device/bluetooth/bluetooth_local_gatt_descriptor.h
+++ b/device/bluetooth/bluetooth_local_gatt_descriptor.h
@@ -6,7 +6,6 @@
 #define DEVICE_BLUETOOTH_BLUETOOTH_LOCAL_GATT_DESCRIPTOR_H_
 
 #include <stdint.h>
-#include <vector>
 
 #include "base/macros.h"
 #include "device/bluetooth/bluetooth_export.h"
diff --git a/device/bluetooth/dbus/bluetooth_profile_service_provider.h b/device/bluetooth/dbus/bluetooth_profile_service_provider.h
index e0ebd4e..c5417f06 100644
--- a/device/bluetooth/dbus/bluetooth_profile_service_provider.h
+++ b/device/bluetooth/dbus/bluetooth_profile_service_provider.h
@@ -7,9 +7,6 @@
 
 #include <stdint.h>
 
-#include <memory>
-#include <string>
-
 #include "base/callback.h"
 #include "base/files/scoped_file.h"
 #include "base/macros.h"
diff --git a/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h b/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h
index fd16dc3bd..59c819e 100644
--- a/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h
+++ b/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_BLUETOOTH_DBUS_FAKE_BLUETOOTH_LE_ADVERTISEMENT_SERVICE_PROVIDER_H_
 #define DEVICE_BLUETOOTH_DBUS_FAKE_BLUETOOTH_LE_ADVERTISEMENT_SERVICE_PROVIDER_H_
 
-#include <memory>
-
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
diff --git a/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.h b/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.h
index 1e8a036d..383c224 100644
--- a/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.h
+++ b/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_BLUETOOTH_DBUS_FAKE_BLUETOOTH_PROFILE_SERVICE_PROVIDER_H_
 #define DEVICE_BLUETOOTH_DBUS_FAKE_BLUETOOTH_PROFILE_SERVICE_PROVIDER_H_
 
-#include <memory>
-
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
diff --git a/device/bluetooth/string_util_icu.h b/device/bluetooth/string_util_icu.h
index 63f7d1a0..9dcb11d 100644
--- a/device/bluetooth/string_util_icu.h
+++ b/device/bluetooth/string_util_icu.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_BLUETOOTH_STRING_UTIL_ICU_H_
 #define DEVICE_BLUETOOTH_STRING_UTIL_ICU_H_
 
-#include <string>
-
 #include "base/strings/string_piece.h"
 #include "device/bluetooth/bluetooth_export.h"
 
diff --git a/device/bluetooth/test/mock_bluetooth_socket.h b/device/bluetooth/test/mock_bluetooth_socket.h
index cda80f5..ebbcdd3 100644
--- a/device/bluetooth/test/mock_bluetooth_socket.h
+++ b/device/bluetooth/test/mock_bluetooth_socket.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_SOCKET_H_
 #define DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_SOCKET_H_
 
-#include <string>
-
 #include "device/bluetooth/bluetooth_socket.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 #include "net/base/io_buffer.h"
diff --git a/device/bluetooth/test/test_bluetooth_advertisement_observer.h b/device/bluetooth/test/test_bluetooth_advertisement_observer.h
index d981a2cc..c123d2d 100644
--- a/device/bluetooth/test/test_bluetooth_advertisement_observer.h
+++ b/device/bluetooth/test/test_bluetooth_advertisement_observer.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_BLUETOOTH_TEST_TEST_BLUETOOTH_ADVERTISEMENT_OBSERVER_H_
 #define DEVICE_BLUETOOTH_TEST_TEST_BLUETOOTH_ADVERTISEMENT_OBSERVER_H_
 
-#include <utility>
-
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "device/bluetooth/bluetooth_advertisement.h"
diff --git a/device/fido/ble_adapter_manager.h b/device/fido/ble_adapter_manager.h
index 70e834e..0887c5f0 100644
--- a/device/fido/ble_adapter_manager.h
+++ b/device/fido/ble_adapter_manager.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_FIDO_BLE_ADAPTER_MANAGER_H_
 #define DEVICE_FIDO_BLE_ADAPTER_MANAGER_H_
 
-#include <string>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
diff --git a/device/fido/fido_task.h b/device/fido/fido_task.h
index d4295a8..acf467c69 100644
--- a/device/fido/fido_task.h
+++ b/device/fido/fido_task.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <vector>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
diff --git a/device/gamepad/gamepad_platform_data_fetcher.h b/device/gamepad/gamepad_platform_data_fetcher.h
index e8f630e9..f59d21f 100644
--- a/device/gamepad/gamepad_platform_data_fetcher.h
+++ b/device/gamepad/gamepad_platform_data_fetcher.h
@@ -8,8 +8,6 @@
 #ifndef DEVICE_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_
 #define DEVICE_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_H_
 
-#include <memory>
-
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "build/build_config.h"
diff --git a/device/gamepad/gamepad_test_helpers.h b/device/gamepad/gamepad_test_helpers.h
index c41ecab2..98873fa 100644
--- a/device/gamepad/gamepad_test_helpers.h
+++ b/device/gamepad/gamepad_test_helpers.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_GAMEPAD_GAMEPAD_TEST_HELPERS_H_
 #define DEVICE_GAMEPAD_GAMEPAD_TEST_HELPERS_H_
 
-#include <memory>
-
 #include "base/macros.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
diff --git a/device/gamepad/test_support/fake_igamepad_statics.h b/device/gamepad/test_support/fake_igamepad_statics.h
index 68aa85a77..3635a6e 100644
--- a/device/gamepad/test_support/fake_igamepad_statics.h
+++ b/device/gamepad/test_support/fake_igamepad_statics.h
@@ -8,8 +8,6 @@
 #include <Windows.Gaming.Input.h>
 #include <wrl.h>
 
-#include <map>
-
 #include "base/containers/flat_map.h"
 
 namespace device {
diff --git a/device/vr/openxr/openxr_statics.h b/device/vr/openxr/openxr_statics.h
index 252824c..da1d5f1b 100644
--- a/device/vr/openxr/openxr_statics.h
+++ b/device/vr/openxr/openxr_statics.h
@@ -6,7 +6,6 @@
 #define DEVICE_VR_OPENXR_OPENXR_STATICS_H_
 
 #include <d3d11.h>
-#include <memory>
 
 #include "build/build_config.h"
 #include "device/vr/openxr/openxr_util.h"
diff --git a/device/vr/orientation/orientation_session.h b/device/vr/orientation/orientation_session.h
index 718a8731..fec8554 100644
--- a/device/vr/orientation/orientation_session.h
+++ b/device/vr/orientation/orientation_session.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_VR_ORIENTATION_ORIENTATION_SESSION_H_
 #define DEVICE_VR_ORIENTATION_ORIENTATION_SESSION_H_
 
-#include <memory>
-
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
diff --git a/device/vr/public/cpp/vr_device_provider.h b/device/vr/public/cpp/vr_device_provider.h
index 0769323..0725439 100644
--- a/device/vr/public/cpp/vr_device_provider.h
+++ b/device/vr/public/cpp/vr_device_provider.h
@@ -5,9 +5,6 @@
 #ifndef DEVICE_VR_PUBLIC_CPP_VR_DEVICE_PROVIDER_H_
 #define DEVICE_VR_PUBLIC_CPP_VR_DEVICE_PROVIDER_H_
 
-#include <memory>
-#include <vector>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "device/vr/public/cpp/xr_frame_sink_client.h"
diff --git a/device/vr/util/sliding_average.h b/device/vr/util/sliding_average.h
index a21e06c..8eed606 100644
--- a/device/vr/util/sliding_average.h
+++ b/device/vr/util/sliding_average.h
@@ -5,8 +5,6 @@
 #ifndef DEVICE_VR_UTIL_SLIDING_AVERAGE_H_
 #define DEVICE_VR_UTIL_SLIDING_AVERAGE_H_
 
-#include <vector>
-
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/time/time.h"
diff --git a/device/vr/vr_device_base.h b/device/vr/vr_device_base.h
index aa0e2a9..c3b0d17 100644
--- a/device/vr/vr_device_base.h
+++ b/device/vr/vr_device_base.h
@@ -5,9 +5,6 @@
 #ifndef DEVICE_VR_VR_DEVICE_BASE_H_
 #define DEVICE_VR_VR_DEVICE_BASE_H_
 
-#include <memory>
-#include <vector>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
diff --git a/docs/linux/chromium_packages.md b/docs/linux/chromium_packages.md
index 4a5a47f..4f48363 100644
--- a/docs/linux/chromium_packages.md
+++ b/docs/linux/chromium_packages.md
@@ -2,7 +2,7 @@
 
 Some Linux distributions package up Chromium for easy installation. Please note
 that Chromium is not identical to Google Chrome -- see
-[chromium_browser_vs_google_chrome.md](chromium_browser_vs_google_chrome.md) --
+[chromium_browser_vs_google_chrome.md](../chromium_browser_vs_google_chrome.md) --
 and that distributions may (and actually do) make their own modifications.
 
 TODO: Move away from tables.
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
index 04818b0c..0c27fd5 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -12,7 +12,6 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
@@ -20,6 +19,7 @@
 #include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_configuration_handler.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_handler_test_helper.h"
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
 #include "components/onc/onc_constants.h"
@@ -66,24 +66,14 @@
   void SetUp() override {
     ApiUnitTest::SetUp();
 
-    chromeos::DBusThreadManager::Initialize();
-    chromeos::NetworkHandler::Initialize();
-
     chromeos::LoginState::Initialize();
     chromeos::LoginState::Get()->SetLoggedInStateAndPrimaryUser(
         chromeos::LoginState::LOGGED_IN_ACTIVE,
         chromeos::LoginState::LOGGED_IN_USER_KIOSK_APP, kUserHash);
-
-    chromeos::DBusThreadManager* dbus_manager =
-        chromeos::DBusThreadManager::Get();
-    profile_test_ = dbus_manager->GetShillProfileClient()->GetTestInterface();
-    service_test_ = dbus_manager->GetShillServiceClient()->GetTestInterface();
-    device_test_ = dbus_manager->GetShillDeviceClient()->GetTestInterface();
-
     base::RunLoop().RunUntilIdle();
 
-    device_test_->ClearDevices();
-    service_test_->ClearServices();
+    device_test()->ClearDevices();
+    service_test()->ClearServices();
 
     SetUpNetworks();
     SetUpNetworkPolicy();
@@ -92,54 +82,52 @@
   }
 
   void TearDown() override {
-    chromeos::NetworkHandler::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
     chromeos::LoginState::Shutdown();
 
     ApiUnitTest::TearDown();
   }
 
   void SetUpNetworks() {
-    profile_test_->AddProfile(kUserProfilePath, kUserHash);
-    profile_test_->AddProfile(
+    profile_test()->AddProfile(kUserProfilePath, kUserHash);
+    profile_test()->AddProfile(
         chromeos::ShillProfileClient::GetSharedProfilePath(), "");
 
-    device_test_->AddDevice(kWifiDevicePath, shill::kTypeWifi, "wifi_device1");
+    device_test()->AddDevice(kWifiDevicePath, shill::kTypeWifi, "wifi_device1");
 
-    service_test_->AddService(kSharedWifiServicePath, kSharedWifiGuid,
-                              kSharedWifiName, shill::kTypeWifi,
-                              shill::kStateOnline, true /* visible */);
-    service_test_->SetServiceProperty(kSharedWifiServicePath,
-                                      shill::kDeviceProperty,
-                                      base::Value(kWifiDevicePath));
-    service_test_->SetServiceProperty(kSharedWifiServicePath,
-                                      shill::kSecurityClassProperty,
-                                      base::Value("psk"));
-    service_test_->SetServiceProperty(kSharedWifiServicePath,
-                                      shill::kPriorityProperty, base::Value(2));
-    service_test_->SetServiceProperty(
+    service_test()->AddService(kSharedWifiServicePath, kSharedWifiGuid,
+                               kSharedWifiName, shill::kTypeWifi,
+                               shill::kStateOnline, true /* visible */);
+    service_test()->SetServiceProperty(kSharedWifiServicePath,
+                                       shill::kDeviceProperty,
+                                       base::Value(kWifiDevicePath));
+    service_test()->SetServiceProperty(kSharedWifiServicePath,
+                                       shill::kSecurityClassProperty,
+                                       base::Value("psk"));
+    service_test()->SetServiceProperty(
+        kSharedWifiServicePath, shill::kPriorityProperty, base::Value(2));
+    service_test()->SetServiceProperty(
         kSharedWifiServicePath, shill::kProfileProperty,
         base::Value(chromeos::ShillProfileClient::GetSharedProfilePath()));
-    profile_test_->AddService(
+    profile_test()->AddService(
         chromeos::ShillProfileClient::GetSharedProfilePath(),
         kSharedWifiServicePath);
 
-    service_test_->AddService(kPrivateWifiServicePath, kPrivateWifiGuid,
-                              kPrivateWifiName, shill::kTypeWifi,
-                              shill::kStateOnline, true /* visible */);
-    service_test_->SetServiceProperty(kPrivateWifiServicePath,
-                                      shill::kDeviceProperty,
-                                      base::Value(kWifiDevicePath));
-    service_test_->SetServiceProperty(kPrivateWifiServicePath,
-                                      shill::kSecurityClassProperty,
-                                      base::Value("psk"));
-    service_test_->SetServiceProperty(kPrivateWifiServicePath,
-                                      shill::kPriorityProperty, base::Value(2));
+    service_test()->AddService(kPrivateWifiServicePath, kPrivateWifiGuid,
+                               kPrivateWifiName, shill::kTypeWifi,
+                               shill::kStateOnline, true /* visible */);
+    service_test()->SetServiceProperty(kPrivateWifiServicePath,
+                                       shill::kDeviceProperty,
+                                       base::Value(kWifiDevicePath));
+    service_test()->SetServiceProperty(kPrivateWifiServicePath,
+                                       shill::kSecurityClassProperty,
+                                       base::Value("psk"));
+    service_test()->SetServiceProperty(
+        kPrivateWifiServicePath, shill::kPriorityProperty, base::Value(2));
 
-    service_test_->SetServiceProperty(kPrivateWifiServicePath,
-                                      shill::kProfileProperty,
-                                      base::Value(kUserProfilePath));
-    profile_test_->AddService(kUserProfilePath, kPrivateWifiServicePath);
+    service_test()->SetServiceProperty(kPrivateWifiServicePath,
+                                       shill::kProfileProperty,
+                                       base::Value(kUserProfilePath));
+    profile_test()->AddService(kUserProfilePath, kPrivateWifiServicePath);
   }
 
   void SetUpNetworkPolicy() {
@@ -191,14 +179,14 @@
   void SetDeviceProperty(const std::string& device_path,
                          const std::string& name,
                          const base::Value& value) {
-    device_test_->SetDeviceProperty(device_path, name, value,
-                                    /*notify_changed=*/false);
+    device_test()->SetDeviceProperty(device_path, name, value,
+                                     /*notify_changed=*/false);
   }
 
   void SetUpCellular() {
     // Add a Cellular GSM Device.
-    device_test_->AddDevice(kCellularDevicePath, shill::kTypeCellular,
-                            "stub_cellular_device1");
+    device_test()->AddDevice(kCellularDevicePath, shill::kTypeCellular,
+                             "stub_cellular_device1");
 
     base::DictionaryValue home_provider;
     home_provider.SetString("name", "Cellular1_Provider");
@@ -236,31 +224,31 @@
     SetDeviceProperty(kCellularDevicePath, shill::kCellularApnListProperty,
                       *apn_list);
 
-    service_test_->AddService(kCellularServicePath, kCellularGuid,
-                              kCellularName, shill::kTypeCellular,
-                              shill::kStateOnline, true /* visible */);
-    service_test_->SetServiceProperty(
+    service_test()->AddService(kCellularServicePath, kCellularGuid,
+                               kCellularName, shill::kTypeCellular,
+                               shill::kStateOnline, true /* visible */);
+    service_test()->SetServiceProperty(
         kCellularServicePath, shill::kAutoConnectProperty, base::Value(true));
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(
         kCellularServicePath, shill::kNetworkTechnologyProperty,
         base::Value(shill::kNetworkTechnologyGsm));
-    service_test_->SetServiceProperty(kCellularServicePath,
-                                      shill::kRoamingStateProperty,
-                                      base::Value(shill::kRoamingStateHome));
-    service_test_->SetServiceProperty(kCellularServicePath,
-                                      shill::kCellularApnProperty, *apn);
-    service_test_->SetServiceProperty(
+    service_test()->SetServiceProperty(kCellularServicePath,
+                                       shill::kRoamingStateProperty,
+                                       base::Value(shill::kRoamingStateHome));
+    service_test()->SetServiceProperty(kCellularServicePath,
+                                       shill::kCellularApnProperty, *apn);
+    service_test()->SetServiceProperty(
         kCellularServicePath, shill::kCellularLastGoodApnProperty, *apn);
 
-    profile_test_->AddService(kUserProfilePath, kCellularServicePath);
+    profile_test()->AddService(kUserProfilePath, kCellularServicePath);
 
     base::RunLoop().RunUntilIdle();
   }
 
   void AddSharedNetworkToUserProfile(const std::string& service_path) {
-    service_test_->SetServiceProperty(service_path, shill::kProfileProperty,
-                                      base::Value(kUserProfilePath));
-    profile_test_->AddService(kUserProfilePath, service_path);
+    service_test()->SetServiceProperty(service_path, shill::kProfileProperty,
+                                       base::Value(kUserProfilePath));
+    profile_test()->AddService(kUserProfilePath, service_path);
 
     base::RunLoop().RunUntilIdle();
   }
@@ -276,7 +264,7 @@
 
   bool GetServiceProfile(const std::string& service_path,
                          std::string* profile_path) {
-    return profile_test_->GetService(service_path, profile_path).is_dict();
+    return profile_test()->GetService(service_path, profile_path).is_dict();
   }
 
   std::unique_ptr<base::DictionaryValue> GetNetworkProperties(
@@ -346,10 +334,18 @@
     return ui_data->GetString("user_settings." + key, value);
   }
 
+  chromeos::ShillServiceClient::TestInterface* service_test() {
+    return network_handler_test_helper_.service_test();
+  }
+  chromeos::ShillDeviceClient::TestInterface* device_test() {
+    return network_handler_test_helper_.device_test();
+  }
+  chromeos::ShillProfileClient::TestInterface* profile_test() {
+    return network_handler_test_helper_.profile_test();
+  }
+
  private:
-  chromeos::ShillProfileClient::TestInterface* profile_test_;
-  chromeos::ShillServiceClient::TestInterface* service_test_;
-  chromeos::ShillDeviceClient::TestInterface* device_test_;
+  chromeos::NetworkHandlerTestHelper network_handler_test_helper_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateApiTest);
 };
diff --git a/gpu/command_buffer/service/shared_image_backing_d3d.cc b/gpu/command_buffer/service/shared_image_backing_d3d.cc
index 893f4d19..1f98a72 100644
--- a/gpu/command_buffer/service/shared_image_backing_d3d.cc
+++ b/gpu/command_buffer/service/shared_image_backing_d3d.cc
@@ -4,6 +4,7 @@
 
 #include "gpu/command_buffer/service/shared_image_backing_d3d.h"
 
+#include "base/memory/ptr_util.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/resource_sizes.h"
@@ -17,26 +18,353 @@
 
 namespace {
 
-class ScopedRestoreTexture2D {
+bool SupportsVideoFormat(DXGI_FORMAT dxgi_format) {
+  switch (dxgi_format) {
+    case DXGI_FORMAT_NV12:
+    case DXGI_FORMAT_P010:
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+      return true;
+    default:
+      return false;
+  }
+}
+
+size_t NumPlanes(DXGI_FORMAT dxgi_format) {
+  switch (dxgi_format) {
+    case DXGI_FORMAT_NV12:
+    case DXGI_FORMAT_P010:
+      return 2;
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+      return 1;
+    default:
+      NOTREACHED();
+      return 0;
+  }
+}
+
+viz::ResourceFormat PlaneFormat(DXGI_FORMAT dxgi_format, size_t plane) {
+  DCHECK_LT(plane, NumPlanes(dxgi_format));
+  switch (dxgi_format) {
+    // TODO(crbug.com/1011555): P010 formats are not fully supported by Skia.
+    // Treat them the same as NV12 for the time being.
+    case DXGI_FORMAT_NV12:
+    case DXGI_FORMAT_P010:
+      // Y plane is accessed as R8 and UV plane is accessed as RG88 in D3D.
+      return plane == 0 ? viz::RED_8 : viz::RG_88;
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+      return viz::BGRA_8888;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+      return viz::RGBA_1010102;
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+      return viz::RGBA_F16;
+    default:
+      NOTREACHED();
+      return viz::BGRA_8888;
+  }
+}
+
+gfx::Size PlaneSize(DXGI_FORMAT dxgi_format,
+                    const gfx::Size& size,
+                    size_t plane) {
+  DCHECK_LT(plane, NumPlanes(dxgi_format));
+  switch (dxgi_format) {
+    case DXGI_FORMAT_NV12:
+    case DXGI_FORMAT_P010:
+      // Y plane is full size and UV plane is accessed as half size in D3D.
+      return plane == 0 ? size : gfx::Size(size.width() / 2, size.height() / 2);
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+      return size;
+    default:
+      NOTREACHED();
+      return gfx::Size();
+  }
+}
+
+class ScopedRestoreTexture {
  public:
-  explicit ScopedRestoreTexture2D(gl::GLApi* api) : api_(api) {
+  ScopedRestoreTexture(gl::GLApi* api, GLenum target)
+      : api_(api), target_(target) {
+    DCHECK(target == GL_TEXTURE_2D || target == GL_TEXTURE_EXTERNAL_OES);
     GLint binding = 0;
-    api->glGetIntegervFn(GL_TEXTURE_BINDING_2D, &binding);
+    api->glGetIntegervFn(target == GL_TEXTURE_2D
+                             ? GL_TEXTURE_BINDING_2D
+                             : GL_TEXTURE_BINDING_EXTERNAL_OES,
+                         &binding);
     prev_binding_ = binding;
   }
 
-  ~ScopedRestoreTexture2D() {
-    api_->glBindTextureFn(GL_TEXTURE_2D, prev_binding_);
-  }
+  ~ScopedRestoreTexture() { api_->glBindTextureFn(target_, prev_binding_); }
 
  private:
   gl::GLApi* const api_;
+  const GLenum target_;
   GLuint prev_binding_ = 0;
-  DISALLOW_COPY_AND_ASSIGN(ScopedRestoreTexture2D);
+  DISALLOW_COPY_AND_ASSIGN(ScopedRestoreTexture);
 };
 
+scoped_refptr<gles2::TexturePassthrough> CreateGLTexture(
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = nullptr,
+    GLenum texture_target = GL_TEXTURE_2D,
+    unsigned array_slice = 0u,
+    unsigned plane_index = 0u) {
+  gl::GLApi* const api = gl::g_current_gl_context;
+  ScopedRestoreTexture scoped_restore(api, texture_target);
+
+  GLuint service_id = 0;
+  api->glGenTexturesFn(1, &service_id);
+  api->glBindTextureFn(texture_target, service_id);
+
+  // The GL internal format can differ from the underlying swap chain or texture
+  // format e.g. RGBA or RGB instead of BGRA or RED/RG for NV12 texture planes.
+  // See EGL_ANGLE_d3d_texture_client_buffer spec for format restrictions.
+  const auto internal_format = viz::GLInternalFormat(format);
+  const auto data_type = viz::GLDataType(format);
+  auto image = base::MakeRefCounted<gl::GLImageD3D>(
+      size, internal_format, data_type, color_space, d3d11_texture, array_slice,
+      plane_index, swap_chain);
+  DCHECK_EQ(image->GetDataFormat(), viz::GLDataFormat(format));
+  if (!image->Initialize()) {
+    DLOG(ERROR) << "GLImageD3D::Initialize failed";
+    api->glDeleteTexturesFn(1, &service_id);
+    return nullptr;
+  }
+  if (!image->BindTexImage(texture_target)) {
+    DLOG(ERROR) << "GLImageD3D::BindTexImage failed";
+    api->glDeleteTexturesFn(1, &service_id);
+    return nullptr;
+  }
+
+  auto texture = base::MakeRefCounted<gles2::TexturePassthrough>(
+      service_id, texture_target);
+  texture->SetLevelImage(texture_target, 0, image.get());
+  GLint texture_memory_size = 0;
+  api->glGetTexParameterivFn(texture_target, GL_MEMORY_SIZE_ANGLE,
+                             &texture_memory_size);
+  texture->SetEstimatedSize(texture_memory_size);
+
+  return texture;
+}
+
 }  // anonymous namespace
 
+SharedImageBackingD3D::SharedState::SharedState(
+    base::win::ScopedHandle shared_handle,
+    Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex)
+    : shared_handle_(std::move(shared_handle)),
+      dxgi_keyed_mutex_(std::move(dxgi_keyed_mutex)) {}
+
+SharedImageBackingD3D::SharedState::~SharedState() {
+  DCHECK(!acquired_for_d3d12_);
+  DCHECK_EQ(acquired_for_d3d11_count_, 0);
+  shared_handle_.Close();
+}
+
+bool SharedImageBackingD3D::SharedState::BeginAccessD3D12(
+    uint64_t* acquire_key) {
+  if (!dxgi_keyed_mutex_) {
+    DLOG(ERROR) << "D3D12 access not supported without keyed mutex";
+    return false;
+  }
+  if (acquired_for_d3d12_ || acquired_for_d3d11_count_ > 0) {
+    DLOG(ERROR) << "Recursive BeginAccess not supported";
+    return false;
+  }
+  *acquire_key = acquire_key_;
+  acquire_key_++;
+  acquired_for_d3d12_ = true;
+  return true;
+}
+
+void SharedImageBackingD3D::SharedState::EndAccessD3D12() {
+  acquired_for_d3d12_ = false;
+}
+
+bool SharedImageBackingD3D::SharedState::BeginAccessD3D11() {
+  // Nop for shared images that are created without keyed mutex (D3D11 only).
+  if (!dxgi_keyed_mutex_)
+    return true;
+
+  if (acquired_for_d3d12_) {
+    DLOG(ERROR) << "Recursive BeginAccess not supported";
+    return false;
+  }
+  if (acquired_for_d3d11_count_ > 0) {
+    acquired_for_d3d11_count_++;
+    return true;
+  }
+  const HRESULT hr = dxgi_keyed_mutex_->AcquireSync(acquire_key_, INFINITE);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Unable to acquire the keyed mutex " << std::hex << hr;
+    return false;
+  }
+  acquire_key_++;
+  acquired_for_d3d11_count_++;
+  return true;
+}
+
+void SharedImageBackingD3D::SharedState::EndAccessD3D11() {
+  // Nop for shared images that are created without keyed mutex (D3D11 only).
+  if (!dxgi_keyed_mutex_)
+    return;
+
+  DCHECK_GT(acquired_for_d3d11_count_, 0);
+  acquired_for_d3d11_count_--;
+  if (acquired_for_d3d11_count_ == 0) {
+    const HRESULT hr = dxgi_keyed_mutex_->ReleaseSync(acquire_key_);
+    if (FAILED(hr))
+      DLOG(ERROR) << "Unable to release the keyed mutex " << std::hex << hr;
+  }
+}
+
+HANDLE SharedImageBackingD3D::SharedState::GetSharedHandle() const {
+  return shared_handle_.Get();
+}
+
+// static
+std::unique_ptr<SharedImageBackingD3D>
+SharedImageBackingD3D::CreateFromSwapChainBuffer(
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    GrSurfaceOrigin surface_origin,
+    SkAlphaType alpha_type,
+    uint32_t usage,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
+    size_t buffer_index) {
+  auto gl_texture =
+      CreateGLTexture(format, size, color_space, d3d11_texture, swap_chain);
+  if (!gl_texture) {
+    DLOG(ERROR) << "Failed to create GL texture";
+    return nullptr;
+  }
+  return base::WrapUnique(new SharedImageBackingD3D(
+      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
+      std::move(d3d11_texture), std::move(gl_texture), std::move(swap_chain),
+      buffer_index));
+}
+
+// static
+std::unique_ptr<SharedImageBackingD3D>
+SharedImageBackingD3D::CreateFromSharedHandle(
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    GrSurfaceOrigin surface_origin,
+    SkAlphaType alpha_type,
+    uint32_t usage,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    base::win::ScopedHandle shared_handle) {
+  DCHECK(shared_handle.IsValid());
+  // Keyed mutexes are required for Dawn interop but are not used for XR
+  // composition where fences are used instead.
+  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex;
+  d3d11_texture.As(&dxgi_keyed_mutex);
+  DCHECK(!(usage & SHARED_IMAGE_USAGE_WEBGPU) || dxgi_keyed_mutex);
+
+  auto shared_state = base::MakeRefCounted<SharedState>(
+      std::move(shared_handle), std::move(dxgi_keyed_mutex));
+
+  // Creating the GL texture doesn't require exclusive access to the underlying
+  // D3D11 texture.
+  auto gl_texture = CreateGLTexture(format, size, color_space, d3d11_texture);
+  if (!gl_texture) {
+    DLOG(ERROR) << "Failed to create GL texture";
+    return nullptr;
+  }
+
+  return base::WrapUnique(new SharedImageBackingD3D(
+      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
+      std::move(d3d11_texture), std::move(gl_texture), /*swap_chain=*/nullptr,
+      /*buffer_index=*/0, std::move(shared_state)));
+}
+
+std::unique_ptr<SharedImageBackingD3D>
+SharedImageBackingD3D::CreateFromGLTexture(
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    GrSurfaceOrigin surface_origin,
+    SkAlphaType alpha_type,
+    uint32_t usage,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    scoped_refptr<gles2::TexturePassthrough> gl_texture) {
+  return base::WrapUnique(new SharedImageBackingD3D(
+      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
+      std::move(d3d11_texture), std::move(gl_texture)));
+}
+
+// static
+std::vector<std::unique_ptr<SharedImageBackingD3D>>
+SharedImageBackingD3D::CreateFromVideoTexture(
+    base::span<const Mailbox> mailboxes,
+    DXGI_FORMAT dxgi_format,
+    const gfx::Size& size,
+    uint32_t usage,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    unsigned array_slice,
+    base::win::ScopedHandle shared_handle) {
+  DCHECK(SupportsVideoFormat(dxgi_format));
+  DCHECK_EQ(mailboxes.size(), NumPlanes(dxgi_format));
+
+  // Shared handle and keyed mutex are required for Dawn interop.
+  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex;
+  d3d11_texture.As(&dxgi_keyed_mutex);
+  DCHECK(!(usage & gpu::SHARED_IMAGE_USAGE_WEBGPU) ||
+         (shared_handle.IsValid() && dxgi_keyed_mutex));
+
+  // Share the same keyed mutex state for all the plane backings.
+  auto shared_state = base::MakeRefCounted<SharedState>(
+      std::move(shared_handle), std::move(dxgi_keyed_mutex));
+
+  std::vector<std::unique_ptr<SharedImageBackingD3D>> shared_images(
+      NumPlanes(dxgi_format));
+  for (size_t plane_index = 0; plane_index < shared_images.size();
+       plane_index++) {
+    const auto& mailbox = mailboxes[plane_index];
+
+    const auto plane_format = PlaneFormat(dxgi_format, plane_index);
+    const auto plane_size = PlaneSize(dxgi_format, size, plane_index);
+
+    // Shared image does not need to store the colorspace since it is already
+    // stored on the VideoFrame which is provided upon presenting the overlay.
+    // To prevent the developer from mistakenly using it, provide the invalid
+    // value from default-construction.
+    constexpr gfx::ColorSpace kInvalidColorSpace;
+
+    auto gl_texture = CreateGLTexture(
+        plane_format, plane_size, kInvalidColorSpace, d3d11_texture,
+        /*swap_chain=*/nullptr, GL_TEXTURE_EXTERNAL_OES, array_slice,
+        plane_index);
+    if (!gl_texture) {
+      DLOG(ERROR) << "Failed to create GL texture";
+      return {};
+    }
+
+    shared_images[plane_index] = base::WrapUnique(new SharedImageBackingD3D(
+        mailbox, plane_format, plane_size, kInvalidColorSpace,
+        kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, d3d11_texture,
+        std::move(gl_texture), /*swap_chain=*/nullptr, /*buffer_index=*/0,
+        shared_state));
+    shared_images[plane_index]->SetCleared();
+  }
+
+  return shared_images;
+}
+
 SharedImageBackingD3D::SharedImageBackingD3D(
     const Mailbox& mailbox,
     viz::ResourceFormat format,
@@ -45,13 +373,11 @@
     GrSurfaceOrigin surface_origin,
     SkAlphaType alpha_type,
     uint32_t usage,
-    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
-    scoped_refptr<gles2::TexturePassthrough> texture,
-    scoped_refptr<gl::GLImage> image,
-    size_t buffer_index,
     Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
-    base::win::ScopedHandle shared_handle,
-    Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex)
+    scoped_refptr<gles2::TexturePassthrough> gl_texture,
+    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
+    size_t buffer_index,
+    scoped_refptr<SharedState> shared_state)
     : ClearTrackingSharedImageBacking(mailbox,
                                       format,
                                       size,
@@ -59,28 +385,23 @@
                                       surface_origin,
                                       alpha_type,
                                       usage,
-                                      texture->estimated_size(),
+                                      gl_texture->estimated_size(),
                                       false /* is_thread_safe */),
-      swap_chain_(std::move(swap_chain)),
-      texture_(std::move(texture)),
-      image_(std::move(image)),
-      buffer_index_(buffer_index),
       d3d11_texture_(std::move(d3d11_texture)),
-      shared_handle_(std::move(shared_handle)),
-      dxgi_keyed_mutex_(std::move(dxgi_keyed_mutex)) {
-  DCHECK(texture_);
+      gl_texture_(std::move(gl_texture)),
+      swap_chain_(std::move(swap_chain)),
+      buffer_index_(buffer_index),
+      shared_state_(std::move(shared_state)) {
+  DCHECK(gl_texture_);
 }
 
 SharedImageBackingD3D::~SharedImageBackingD3D() {
   if (!have_context())
-    texture_->MarkContextLost();
-  texture_ = nullptr;
-  swap_chain_ = nullptr;
+    gl_texture_->MarkContextLost();
+  gl_texture_ = nullptr;
+  shared_state_ = nullptr;
+  swap_chain_.Reset();
   d3d11_texture_.Reset();
-  dxgi_keyed_mutex_.Reset();
-  keyed_mutex_acquire_key_ = 0;
-  keyed_mutex_acquired_ = false;
-  shared_handle_.Close();
 
 #if BUILDFLAG(USE_DAWN)
   external_image_ = nullptr;
@@ -94,7 +415,7 @@
 
 bool SharedImageBackingD3D::ProduceLegacyMailbox(
     MailboxManager* mailbox_manager) {
-  mailbox_manager->ProduceTexture(mailbox(), texture_.get());
+  mailbox_manager->ProduceTexture(mailbox(), gl_texture_.get());
   return true;
 }
 
@@ -114,7 +435,7 @@
 
   // Persistently open the shared handle by caching it on this backing.
   if (!external_image_) {
-    DCHECK(shared_handle_.IsValid());
+    DCHECK(base::win::HandleTraits::IsHandleValid(GetSharedHandle()));
 
     const viz::ResourceFormat viz_resource_format = format();
     const WGPUTextureFormat wgpu_format =
@@ -136,7 +457,7 @@
     dawn_native::d3d12::ExternalImageDescriptorDXGISharedHandle
         externalImageDesc;
     externalImageDesc.cTextureDescriptor = &texture_descriptor;
-    externalImageDesc.sharedHandle = shared_handle_.Get();
+    externalImageDesc.sharedHandle = GetSharedHandle();
 
     external_image_ = dawn_native::d3d12::ExternalImageDXGI::Create(
         device, &externalImageDesc);
@@ -163,65 +484,38 @@
   // various GPU dumps.
   auto client_guid = GetSharedImageGUIDForTracing(mailbox());
   base::trace_event::MemoryAllocatorDumpGuid service_guid =
-      gl::GetGLTextureServiceGUIDForTracing(texture_->service_id());
+      gl::GetGLTextureServiceGUIDForTracing(gl_texture_->service_id());
   pmd->CreateSharedGlobalAllocatorDump(service_guid);
 
   int importance = 2;  // This client always owns the ref.
   pmd->AddOwnershipEdge(client_guid, service_guid, importance);
 
   // Swap chain textures only have one level backed by an image.
-  image_->OnMemoryDump(pmd, client_tracing_id, dump_name);
+  GetGLImage()->OnMemoryDump(pmd, client_tracing_id, dump_name);
 }
 
 bool SharedImageBackingD3D::BeginAccessD3D12(uint64_t* acquire_key) {
-  if (keyed_mutex_acquired_) {
-    DLOG(ERROR) << "Recursive BeginAccess not supported";
-    return false;
-  }
-  *acquire_key = keyed_mutex_acquire_key_;
-  keyed_mutex_acquire_key_++;
-  keyed_mutex_acquired_ = true;
-  return true;
+  return shared_state_->BeginAccessD3D12(acquire_key);
 }
 
 void SharedImageBackingD3D::EndAccessD3D12() {
-  keyed_mutex_acquired_ = false;
+  shared_state_->EndAccessD3D12();
 }
 
 bool SharedImageBackingD3D::BeginAccessD3D11() {
-  if (dxgi_keyed_mutex_) {
-    if (keyed_mutex_acquired_) {
-      DLOG(ERROR) << "Recursive BeginAccess not supported";
-      return false;
-    }
-    const HRESULT hr =
-        dxgi_keyed_mutex_->AcquireSync(keyed_mutex_acquire_key_, INFINITE);
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "Unable to acquire the keyed mutex " << std::hex << hr;
-      return false;
-    }
-    keyed_mutex_acquire_key_++;
-    keyed_mutex_acquired_ = true;
-  }
-  return true;
+  return shared_state_->BeginAccessD3D11();
 }
+
 void SharedImageBackingD3D::EndAccessD3D11() {
-  if (dxgi_keyed_mutex_) {
-    const HRESULT hr = dxgi_keyed_mutex_->ReleaseSync(keyed_mutex_acquire_key_);
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "Unable to release the keyed mutex " << std::hex << hr;
-      return;
-    }
-    keyed_mutex_acquired_ = false;
-  }
+  shared_state_->EndAccessD3D11();
 }
 
 HANDLE SharedImageBackingD3D::GetSharedHandle() const {
-  return shared_handle_.Get();
+  return shared_state_->GetSharedHandle();
 }
 
 gl::GLImage* SharedImageBackingD3D::GetGLImage() const {
-  return image_.get();
+  return gl_texture_->GetLevelImage(gl_texture_->target(), 0u);
 }
 
 bool SharedImageBackingD3D::PresentSwapChain() {
@@ -244,10 +538,12 @@
   }
 
   gl::GLApi* const api = gl::g_current_gl_context;
-  ScopedRestoreTexture2D scoped_restore(api);
 
-  api->glBindTextureFn(GL_TEXTURE_2D, texture_->service_id());
-  if (!image_->BindTexImage(GL_TEXTURE_2D)) {
+  DCHECK_EQ(gl_texture_->target(), static_cast<unsigned>(GL_TEXTURE_2D));
+  ScopedRestoreTexture scoped_restore(api, GL_TEXTURE_2D);
+
+  api->glBindTextureFn(GL_TEXTURE_2D, gl_texture_->service_id());
+  if (!GetGLImage()->BindTexImage(GL_TEXTURE_2D)) {
     DLOG(ERROR) << "GLImage::BindTexImage failed";
     return false;
   }
@@ -263,7 +559,7 @@
                                                    MemoryTypeTracker* tracker) {
   TRACE_EVENT0("gpu", "SharedImageBackingD3D::ProduceGLTexturePassthrough");
   return std::make_unique<SharedImageRepresentationGLTexturePassthroughD3D>(
-      manager, this, tracker, texture_);
+      manager, this, tracker, gl_texture_);
 }
 
 std::unique_ptr<SharedImageRepresentationSkia>
diff --git a/gpu/command_buffer/service/shared_image_backing_d3d.h b/gpu/command_buffer/service/shared_image_backing_d3d.h
index b6bdb07..5af24d9 100644
--- a/gpu/command_buffer/service/shared_image_backing_d3d.h
+++ b/gpu/command_buffer/service/shared_image_backing_d3d.h
@@ -42,7 +42,7 @@
 class GPU_GLES2_EXPORT SharedImageBackingD3D
     : public ClearTrackingSharedImageBacking {
  public:
-  SharedImageBackingD3D(
+  static std::unique_ptr<SharedImageBackingD3D> CreateFromSwapChainBuffer(
       const Mailbox& mailbox,
       viz::ResourceFormat format,
       const gfx::Size& size,
@@ -50,13 +50,42 @@
       GrSurfaceOrigin surface_origin,
       SkAlphaType alpha_type,
       uint32_t usage,
-      Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
-      scoped_refptr<gles2::TexturePassthrough> texture,
-      scoped_refptr<gl::GLImage> image,
-      size_t buffer_index,
       Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
-      base::win::ScopedHandle shared_handle,
-      Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex);
+      Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
+      size_t buffer_index);
+
+  static std::unique_ptr<SharedImageBackingD3D> CreateFromSharedHandle(
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      GrSurfaceOrigin surface_origin,
+      SkAlphaType alpha_type,
+      uint32_t usage,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      base::win::ScopedHandle shared_handle);
+
+  // TODO(sunnyps): Remove this after migrating DXVA decoder to EGLImage.
+  static std::unique_ptr<SharedImageBackingD3D> CreateFromGLTexture(
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      GrSurfaceOrigin surface_origin,
+      SkAlphaType alpha_type,
+      uint32_t usage,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      scoped_refptr<gles2::TexturePassthrough> gl_texture);
+
+  static std::vector<std::unique_ptr<SharedImageBackingD3D>>
+  CreateFromVideoTexture(
+      base::span<const Mailbox> mailboxes,
+      DXGI_FORMAT dxgi_format,
+      const gfx::Size& size,
+      uint32_t usage,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      unsigned array_slice,
+      base::win::ScopedHandle shared_handle = base::win::ScopedHandle());
 
   ~SharedImageBackingD3D() override;
 
@@ -100,29 +129,64 @@
       scoped_refptr<SharedContextState> context_state) override;
 
  private:
-  uint32_t GetAllowedDawnUsages() const;
+  class SharedState : public base::RefCountedThreadSafe<SharedState> {
+   public:
+    explicit SharedState(
+        base::win::ScopedHandle shared_handle = base::win::ScopedHandle(),
+        Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex = nullptr);
 
-  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;
-  scoped_refptr<gles2::TexturePassthrough> texture_;
-  scoped_refptr<gl::GLImage> image_;
-  const size_t buffer_index_;
+    bool BeginAccessD3D11();
+    void EndAccessD3D11();
+
+    bool BeginAccessD3D12(uint64_t* acquire_key);
+    void EndAccessD3D12();
+
+    HANDLE GetSharedHandle() const;
+
+   private:
+    friend class base::RefCountedThreadSafe<SharedState>;
+    ~SharedState();
+
+    // If |d3d11_texture_| has a keyed mutex, it will be stored in
+    // |dxgi_keyed_mutex_|. The keyed mutex is used to synchronize D3D11 and
+    // D3D12 Chromium components. |dxgi_keyed_mutex_| is the D3D11 side of the
+    // keyed mutex. To create the corresponding D3D12 interface, pass the handle
+    // stored in |shared_handle_| to ID3D12Device::OpenSharedHandle. Only one
+    // component is allowed to read/write to the texture at a time.
+    // |acquire_key_| is incremented on every Acquire/Release usage.
+    base::win::ScopedHandle shared_handle_;
+    Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex_;
+    uint64_t acquire_key_ = 0;
+    bool acquired_for_d3d12_ = false;
+    int acquired_for_d3d11_count_ = 0;
+  };
+
+  SharedImageBackingD3D(
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      GrSurfaceOrigin surface_origin,
+      SkAlphaType alpha_type,
+      uint32_t usage,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      scoped_refptr<gles2::TexturePassthrough> gl_texture,
+      Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = nullptr,
+      size_t buffer_index = 0,
+      scoped_refptr<SharedState> shared_state =
+          base::MakeRefCounted<SharedState>());
+
+  uint32_t GetAllowedDawnUsages() const;
 
   // Texture could be nullptr if an empty backing is needed for testing.
   Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture_;
+  scoped_refptr<gles2::TexturePassthrough> gl_texture_;
 
-  // If d3d11_texture_ has a keyed mutex, it will be stored in
-  // dxgi_keyed_mutex. The keyed mutex is used to synchronize
-  // D3D11 and D3D12 Chromium components.
-  // dxgi_keyed_mutex_ is the D3D11 side of the keyed mutex.
-  // To create the corresponding D3D12 interface, pass the handle
-  // stored in shared_handle_ to ID3D12Device::OpenSharedHandle.
-  // Only one component is allowed to read/write to the texture
-  // at a time. keyed_mutex_acquire_key_ is incremented on every
-  // Acquire/Release usage.
-  base::win::ScopedHandle shared_handle_;
-  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex_;
-  uint64_t keyed_mutex_acquire_key_ = 0;
-  bool keyed_mutex_acquired_ = false;
+  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;
+
+  const size_t buffer_index_;
+
+  scoped_refptr<SharedState> shared_state_;
 
   // If external_image_ exists, it means Dawn produced the D3D12 side of the
   // D3D11 texture created by ID3D12Device::OpenSharedHandle.
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
index 02225c6..e2a8c22 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
@@ -18,24 +18,6 @@
 
 namespace {
 
-class ScopedRestoreTexture2D {
- public:
-  explicit ScopedRestoreTexture2D(gl::GLApi* api) : api_(api) {
-    GLint binding = 0;
-    api->glGetIntegervFn(GL_TEXTURE_BINDING_2D, &binding);
-    prev_binding_ = binding;
-  }
-
-  ~ScopedRestoreTexture2D() {
-    api_->glBindTextureFn(GL_TEXTURE_2D, prev_binding_);
-  }
-
- private:
-  gl::GLApi* const api_;
-  GLuint prev_binding_ = 0;
-  DISALLOW_COPY_AND_ASSIGN(ScopedRestoreTexture2D);
-};
-
 bool ClearBackBuffer(Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain,
                      Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device) {
   Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
@@ -108,80 +90,6 @@
          gl::DirectCompositionSurfaceWin::IsSwapChainTearingSupported();
 }
 
-std::unique_ptr<SharedImageBacking> SharedImageBackingFactoryD3D::MakeBacking(
-    const Mailbox& mailbox,
-    viz::ResourceFormat format,
-    const gfx::Size& size,
-    const gfx::ColorSpace& color_space,
-    GrSurfaceOrigin surface_origin,
-    SkAlphaType alpha_type,
-    uint32_t usage,
-    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
-    size_t buffer_index,
-    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
-    base::win::ScopedHandle shared_handle) {
-  gl::GLApi* const api = gl::g_current_gl_context;
-  ScopedRestoreTexture2D scoped_restore(api);
-
-  const GLenum target = GL_TEXTURE_2D;
-  GLuint service_id = 0;
-  api->glGenTexturesFn(1, &service_id);
-  api->glBindTextureFn(target, service_id);
-  api->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-  api->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-  api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> dxgi_keyed_mutex;
-
-  if (swap_chain) {
-    DCHECK(!d3d11_texture);
-    DCHECK(!shared_handle.IsValid());
-    const HRESULT hr =
-        swap_chain->GetBuffer(buffer_index, IID_PPV_ARGS(&d3d11_texture));
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "GetBuffer failed with error " << std::hex;
-      return nullptr;
-    }
-  } else if (shared_handle.IsValid()) {
-    // Keyed mutexes are required for Dawn interop but are not used
-    // for XR composition where fences are used instead.
-    d3d11_texture.As(&dxgi_keyed_mutex);
-  }
-  DCHECK(d3d11_texture);
-
-  // The GL internal format can differ from the underlying swap chain format
-  // e.g. RGBA8 or RGB8 instead of BGRA8.
-  const GLenum internal_format = viz::GLInternalFormat(format);
-  const GLenum data_type = viz::GLDataType(format);
-  const GLenum data_format = viz::GLDataFormat(format);
-  auto image = base::MakeRefCounted<gl::GLImageD3D>(
-      size, internal_format, data_type, d3d11_texture, swap_chain);
-  DCHECK_EQ(image->GetDataFormat(), data_format);
-  if (!image->Initialize()) {
-    DLOG(ERROR) << "GLImageD3D::Initialize failed";
-    return nullptr;
-  }
-  if (!image->BindTexImage(target)) {
-    DLOG(ERROR) << "GLImageD3D::BindTexImage failed";
-    return nullptr;
-  }
-
-  scoped_refptr<gles2::TexturePassthrough> texture =
-      base::MakeRefCounted<gles2::TexturePassthrough>(service_id, target);
-  texture->SetLevelImage(target, 0, image.get());
-  GLint texture_memory_size = 0;
-  api->glGetTexParameterivFn(target, GL_MEMORY_SIZE_ANGLE,
-                             &texture_memory_size);
-  texture->SetEstimatedSize(texture_memory_size);
-
-  return std::make_unique<SharedImageBackingD3D>(
-      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      std::move(swap_chain), std::move(texture), std::move(image), buffer_index,
-      std::move(d3d11_texture), std::move(shared_handle),
-      std::move(dxgi_keyed_mutex));
-}
-
 SharedImageBackingFactoryD3D::SwapChainBackings
 SharedImageBackingFactoryD3D::CreateSwapChain(
     const Mailbox& front_buffer_mailbox,
@@ -263,18 +171,30 @@
   if (!ClearBackBuffer(swap_chain, d3d11_device_))
     return {nullptr, nullptr};
 
-  auto back_buffer_backing = MakeBacking(
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> back_buffer_texture;
+  hr = swap_chain->GetBuffer(0, IID_PPV_ARGS(&back_buffer_texture));
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "GetBuffer failed with error " << std::hex;
+    return {nullptr, nullptr};
+  }
+  auto back_buffer_backing = SharedImageBackingD3D::CreateFromSwapChainBuffer(
       back_buffer_mailbox, format, size, color_space, surface_origin,
-      alpha_type, usage, swap_chain, 0 /* buffer_index */,
-      nullptr /* d3d11_texture */, base::win::ScopedHandle());
+      alpha_type, usage, std::move(back_buffer_texture), swap_chain,
+      /*buffer_index=*/0);
   if (!back_buffer_backing)
     return {nullptr, nullptr};
   back_buffer_backing->SetCleared();
 
-  auto front_buffer_backing = MakeBacking(
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> front_buffer_texture;
+  hr = swap_chain->GetBuffer(1, IID_PPV_ARGS(&front_buffer_texture));
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "GetBuffer failed with error " << std::hex;
+    return {nullptr, nullptr};
+  }
+  auto front_buffer_backing = SharedImageBackingD3D::CreateFromSwapChainBuffer(
       front_buffer_mailbox, format, size, color_space, surface_origin,
-      alpha_type, usage, swap_chain, 1 /* buffer_index */,
-      nullptr /* d3d11_texture */, base::win::ScopedHandle());
+      alpha_type, usage, std::move(front_buffer_texture), swap_chain,
+      /*buffer_index=*/1);
   if (!front_buffer_backing)
     return {nullptr, nullptr};
   front_buffer_backing->SetCleared();
@@ -345,14 +265,12 @@
                 << std::hex << hr;
     return nullptr;
   }
-
   // Put the shared handle into an RAII object as quickly as possible to
   // ensure we do not leak it.
   base::win::ScopedHandle scoped_shared_handle(shared_handle);
-
-  return MakeBacking(mailbox, format, size, color_space, surface_origin,
-                     alpha_type, usage, nullptr, 0, std::move(d3d11_texture),
-                     std::move(scoped_shared_handle));
+  return SharedImageBackingD3D::CreateFromSharedHandle(
+      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
+      std::move(d3d11_texture), std::move(scoped_shared_handle));
 }
 
 std::unique_ptr<SharedImageBacking>
@@ -427,11 +345,10 @@
     return nullptr;
   }
 
-  auto backing =
-      MakeBacking(mailbox, viz::GetResourceFormat(format), size, color_space,
-                  surface_origin, alpha_type, usage, /*swap_chain=*/nullptr,
-                  /*buffer_index=*/0, std::move(d3d11_texture),
-                  std::move(handle.dxgi_handle));
+  auto backing = SharedImageBackingD3D::CreateFromSharedHandle(
+      mailbox, viz::GetResourceFormat(format), size, color_space,
+      surface_origin, alpha_type, usage, std::move(d3d11_texture),
+      std::move(handle.dxgi_handle));
   if (backing)
     backing->SetCleared();
   return backing;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
index 356d812..c84072b 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
@@ -33,6 +33,15 @@
 #include <dawn_native/DawnNative.h>
 #endif  // BUILDFLAG(USE_DAWN)
 
+#define SCOPED_GL_CLEANUP_VAR(api, func, var)            \
+  base::ScopedClosureRunner delete_##var(base::BindOnce( \
+      [](gl::GLApi* api, GLuint var) { api->gl##func##Fn(var); }, api, var))
+
+#define SCOPED_GL_CLEANUP_PTR(api, func, n, var)                           \
+  base::ScopedClosureRunner delete_##var(base::BindOnce(                   \
+      [](gl::GLApi* api, GLuint var) { api->gl##func##Fn(n, &var); }, api, \
+      var))
+
 namespace gpu {
 namespace {
 
@@ -452,6 +461,8 @@
     }
   }
 
+  void RunVideoTest(bool use_shared_handle);
+
   scoped_refptr<SharedContextState> context_state_;
 };
 
@@ -1038,5 +1049,248 @@
 }
 #endif  // BUILDFLAG(USE_DAWN)
 
+void SharedImageBackingFactoryD3DTest::RunVideoTest(bool use_shared_handle) {
+  if (!IsD3DSharedImageSupported())
+    return;
+
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
+      shared_image_factory_->GetDeviceForTesting();
+
+  const gfx::Size size(32, 32);
+
+  const unsigned kYFillValue = 0x12;
+  const unsigned kUFillValue = 0x23;
+  const unsigned kVFillValue = 0x34;
+
+  const size_t kYPlaneSize = size.width() * size.height();
+
+  std::vector<unsigned char> video_data;
+  video_data.resize(kYPlaneSize * 3 / 2);
+  memset(video_data.data(), kYFillValue, kYPlaneSize);
+  for (size_t i = 0; i < kYPlaneSize / 2; i += 2) {
+    video_data[kYPlaneSize + i] = kUFillValue;
+    video_data[kYPlaneSize + i + 1] = kVFillValue;
+  }
+
+  D3D11_SUBRESOURCE_DATA data = {};
+  data.pSysMem = static_cast<const void*>(video_data.data());
+  data.SysMemPitch = static_cast<UINT>(size.width());
+
+  CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_NV12, size.width(), size.height(), 1,
+                             1, D3D11_BIND_SHADER_RESOURCE);
+  if (use_shared_handle) {
+    desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
+                     D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
+  }
+
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
+  HRESULT hr = d3d11_device->CreateTexture2D(&desc, &data, &d3d11_texture);
+  ASSERT_TRUE(SUCCEEDED(hr));
+
+  uint32_t usage =
+      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
+      gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
+      gpu::SHARED_IMAGE_USAGE_SCANOUT;
+
+  base::win::ScopedHandle shared_handle;
+  if (use_shared_handle) {
+    Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
+    hr = d3d11_texture.As(&dxgi_resource);
+    ASSERT_TRUE(SUCCEEDED(hr));
+
+    HANDLE handle;
+    hr = dxgi_resource->CreateSharedHandle(
+        nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
+        nullptr, &handle);
+    ASSERT_TRUE(SUCCEEDED(hr));
+
+    shared_handle.Set(handle);
+    ASSERT_TRUE(shared_handle.IsValid());
+
+    usage |= gpu::SHARED_IMAGE_USAGE_WEBGPU;
+  }
+
+  const gpu::Mailbox mailboxes[] = {gpu::Mailbox::GenerateForSharedImage(),
+                                    gpu::Mailbox::GenerateForSharedImage()};
+
+  auto shared_image_backings = SharedImageBackingD3D::CreateFromVideoTexture(
+      mailboxes, DXGI_FORMAT_NV12, size, usage, d3d11_texture,
+      /*array_slice=*/0, std::move(shared_handle));
+  ASSERT_EQ(shared_image_backings.size(), 2u);
+
+  const gfx::Size plane_sizes[] = {
+      size, gfx::Size(size.width() / 2, size.height() / 2)};
+  const viz::ResourceFormat plane_formats[] = {viz::RED_8, viz::RG_88};
+
+  std::vector<std::unique_ptr<SharedImageRepresentationFactoryRef>>
+      shared_image_refs;
+  for (size_t i = 0; i < shared_image_backings.size(); i++) {
+    auto& backing = shared_image_backings[i];
+
+    EXPECT_EQ(backing->mailbox(), mailboxes[i]);
+    EXPECT_EQ(backing->size(), plane_sizes[i]);
+    EXPECT_EQ(backing->format(), plane_formats[i]);
+    EXPECT_EQ(backing->color_space(), gfx::ColorSpace());
+    EXPECT_EQ(backing->surface_origin(), kTopLeft_GrSurfaceOrigin);
+    EXPECT_EQ(backing->alpha_type(), kPremul_SkAlphaType);
+    EXPECT_EQ(backing->usage(), usage);
+    EXPECT_TRUE(backing->IsCleared());
+
+    shared_image_refs.push_back(shared_image_manager_.Register(
+        std::move(backing), memory_type_tracker_.get()));
+  }
+
+  // Setup GL shaders, framebuffers, uniforms, etc.
+  static const char* kVideoFragmentShaderSrc =
+      "#extension GL_OES_EGL_image_external : require\n"
+      "precision mediump float;\n"
+      "uniform samplerExternalOES u_texture_y;\n"
+      "uniform samplerExternalOES u_texture_uv;\n"
+      "varying vec2 v_texCoord;\n"
+      "void main() {\n"
+      "  gl_FragColor.r = texture2D(u_texture_y, v_texCoord).r;\n"
+      "  gl_FragColor.gb = texture2D(u_texture_uv, v_texCoord).rg;\n"
+      "  gl_FragColor.a = 1.0;\n"
+      "}\n";
+
+  gl::GLApi* api = gl::g_current_gl_context;
+
+  GLint status = 0;
+  GLuint vertex_shader = api->glCreateShaderFn(GL_VERTEX_SHADER);
+  SCOPED_GL_CLEANUP_VAR(api, DeleteShader, vertex_shader);
+  ASSERT_NE(vertex_shader, 0u);
+  api->glShaderSourceFn(vertex_shader, 1, &kVertexShaderSrc, nullptr);
+  api->glCompileShaderFn(vertex_shader);
+  api->glGetShaderivFn(vertex_shader, GL_COMPILE_STATUS, &status);
+  ASSERT_NE(status, 0);
+
+  GLuint fragment_shader = api->glCreateShaderFn(GL_FRAGMENT_SHADER);
+  SCOPED_GL_CLEANUP_VAR(api, DeleteShader, fragment_shader);
+  ASSERT_NE(fragment_shader, 0u);
+  api->glShaderSourceFn(fragment_shader, 1, &kVideoFragmentShaderSrc, nullptr);
+  api->glCompileShaderFn(fragment_shader);
+  api->glGetShaderivFn(fragment_shader, GL_COMPILE_STATUS, &status);
+  ASSERT_NE(status, 0);
+
+  GLuint program = api->glCreateProgramFn();
+  ASSERT_NE(program, 0u);
+  SCOPED_GL_CLEANUP_VAR(api, DeleteProgram, program);
+  api->glAttachShaderFn(program, vertex_shader);
+  api->glAttachShaderFn(program, fragment_shader);
+  api->glLinkProgramFn(program);
+  api->glGetProgramivFn(program, GL_LINK_STATUS, &status);
+  ASSERT_NE(status, 0);
+
+  GLint vertex_location = api->glGetAttribLocationFn(program, "a_position");
+  ASSERT_NE(vertex_location, -1);
+
+  GLint y_texture_location =
+      api->glGetUniformLocationFn(program, "u_texture_y");
+  ASSERT_NE(y_texture_location, -1);
+
+  GLint uv_texture_location =
+      api->glGetUniformLocationFn(program, "u_texture_uv");
+  ASSERT_NE(uv_texture_location, -1);
+
+  GLuint fbo, renderbuffer = 0u;
+  api->glGenFramebuffersEXTFn(1, &fbo);
+  ASSERT_NE(fbo, 0u);
+  SCOPED_GL_CLEANUP_PTR(api, DeleteFramebuffersEXT, 1, fbo);
+  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);
+
+  api->glGenRenderbuffersEXTFn(1, &renderbuffer);
+  ASSERT_NE(renderbuffer, 0u);
+  SCOPED_GL_CLEANUP_PTR(api, DeleteRenderbuffersEXT, 1, renderbuffer);
+  api->glBindRenderbufferEXTFn(GL_RENDERBUFFER, renderbuffer);
+
+  api->glRenderbufferStorageEXTFn(GL_RENDERBUFFER, GL_RGBA8_OES, size.width(),
+                                  size.height());
+  api->glFramebufferRenderbufferEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                      GL_RENDERBUFFER, renderbuffer);
+  ASSERT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
+            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
+
+  // Set the clear color to green.
+  api->glViewportFn(0, 0, size.width(), size.height());
+  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
+  api->glClearFn(GL_COLOR_BUFFER_BIT);
+
+  GLuint vbo = 0u;
+  api->glGenBuffersARBFn(1, &vbo);
+  ASSERT_NE(vbo, 0u);
+  SCOPED_GL_CLEANUP_PTR(api, DeleteBuffersARB, 1, vbo);
+  api->glBindBufferFn(GL_ARRAY_BUFFER, vbo);
+  static const float vertices[] = {
+      1.0f, 1.0f, -1.0f, 1.0f,  -1.0f, -1.0f,
+      1.0f, 1.0f, -1.0f, -1.0f, 1.0f,  -1.0f,
+  };
+  api->glBufferDataFn(GL_ARRAY_BUFFER, sizeof(vertices), vertices,
+                      GL_STATIC_DRAW);
+
+  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));
+
+  // Create the representations for the planes, get the texture ids, bind to
+  // samplers, and draw.
+  {
+    auto y_texture =
+        shared_image_representation_factory_->ProduceGLTexturePassthrough(
+            mailboxes[0]);
+    ASSERT_NE(y_texture, nullptr);
+
+    auto uv_texture =
+        shared_image_representation_factory_->ProduceGLTexturePassthrough(
+            mailboxes[1]);
+    ASSERT_NE(uv_texture, nullptr);
+
+    auto y_texture_access = y_texture->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    ASSERT_NE(y_texture_access, nullptr);
+
+    auto uv_texture_access = uv_texture->BeginScopedAccess(
+        GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
+        SharedImageRepresentation::AllowUnclearedAccess::kNo);
+    ASSERT_NE(uv_texture_access, nullptr);
+
+    api->glActiveTextureFn(GL_TEXTURE0);
+    api->glBindTextureFn(GL_TEXTURE_EXTERNAL_OES,
+                         y_texture->GetTextureBase()->service_id());
+    ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));
+
+    api->glActiveTextureFn(GL_TEXTURE1);
+    api->glBindTextureFn(GL_TEXTURE_EXTERNAL_OES,
+                         uv_texture->GetTextureBase()->service_id());
+    ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));
+
+    api->glUseProgramFn(program);
+
+    api->glEnableVertexAttribArrayFn(vertex_location);
+    api->glVertexAttribPointerFn(vertex_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+    api->glUniform1iFn(y_texture_location, 0);
+    api->glUniform1iFn(uv_texture_location, 1);
+
+    api->glDrawArraysFn(GL_TRIANGLES, 0, 6);
+    ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));
+
+    GLubyte pixel_color[4];
+    api->glReadPixelsFn(size.width() / 2, size.height() / 2, 1, 1, GL_RGBA,
+                        GL_UNSIGNED_BYTE, pixel_color);
+    EXPECT_EQ(kYFillValue, pixel_color[0]);
+    EXPECT_EQ(kUFillValue, pixel_color[1]);
+    EXPECT_EQ(kVFillValue, pixel_color[2]);
+    EXPECT_EQ(0xff, pixel_color[3]);
+  }
+  // TODO(dawn:551): Test Dawn access after multi-planar support lands in Dawn.
+}
+
+TEST_F(SharedImageBackingFactoryD3DTest, CreateFromVideoTexture) {
+  RunVideoTest(/*use_shared_handle=*/false);
+}
+
+TEST_F(SharedImageBackingFactoryD3DTest, CreateFromVideoTextureSharedHandle) {
+  RunVideoTest(/*use_shared_handle=*/true);
+}
+
 }  // anonymous namespace
 }  // namespace gpu
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index cbb9580..5c56abf 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -38143,7 +38143,7 @@
       dimensions: "builder:cast_shell_linux"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -39950,7 +39950,7 @@
       dimensions: "builder:fuchsia-x64-cast"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -40014,7 +40014,7 @@
       dimensions: "builder:fuchsia_arm64"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -40078,7 +40078,7 @@
       dimensions: "builder:fuchsia_x64"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -45794,7 +45794,7 @@
       dimensions: "builderless:1"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:0"
       exe {
@@ -45859,7 +45859,7 @@
       dimensions: "builderless:1"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:0"
       exe {
@@ -47219,7 +47219,7 @@
       dimensions: "builder:linux-libfuzzer-asan-rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -47478,7 +47478,7 @@
       dimensions: "builder:linux-ozone-rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -49412,7 +49412,7 @@
       dimensions: "builder:linux_chromium_compile_dbg_ng"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
diff --git a/infra/config/lib/try.star b/infra/config/lib/try.star
index 010b3cc..21a8378 100644
--- a/infra/config/lib/try.star
+++ b/infra/config/lib/try.star
@@ -194,6 +194,7 @@
         )
 
 def blink_builder(*, name, goma_backend = None, **kwargs):
+    kwargs.setdefault("os", builders.os.LINUX_BIONIC_REMOVE)
     return try_builder(
         name = name,
         builder_group = "tryserver.blink",
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 1e55de2..c6d43a5e 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1005,6 +1005,8 @@
     builderless = not settings.is_main,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1108,6 +1110,8 @@
     builderless = not settings.is_main,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1116,6 +1120,8 @@
     builderless = not settings.is_main,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1124,6 +1130,8 @@
     builderless = not settings.is_main,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1243,6 +1251,8 @@
     executable = "recipe:chromium_libfuzzer_trybot",
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1251,6 +1261,8 @@
     builderless = not settings.is_main,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
@@ -1415,6 +1427,8 @@
     goma_jobs = goma.jobs.J150,
     main_list_view = "try",
     tryjob = try_.job(),
+    # TODO(crbug/1202745)
+    os = os.LINUX_XENIAL_OR_BIONIC_REMOVE,
 )
 
 try_.chromium_linux_builder(
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index fae2dba0..193be82 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -212,6 +212,7 @@
     "//build:branding_buildflags",
     "//components/bookmarks/browser",
     "//components/breadcrumbs/core",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/browser_sync",
     "//components/browsing_data/core",
     "//components/component_updater",
@@ -254,7 +255,6 @@
     "//ios/chrome/browser/crash_report",
     "//ios/chrome/browser/crash_report:crash_report_internal",
     "//ios/chrome/browser/crash_report/breadcrumbs",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/credential_provider:buildflags",
     "//ios/chrome/browser/download",
     "//ios/chrome/browser/external_files",
diff --git a/ios/chrome/app/main_application_delegate.mm b/ios/chrome/app/main_application_delegate.mm
index bca4705..c5e7473 100644
--- a/ios/chrome/app/main_application_delegate.mm
+++ b/ios/chrome/app/main_application_delegate.mm
@@ -331,29 +331,14 @@
 #pragma mark Continuing User Activity and Handling Quick Actions
 
 - (BOOL)application:(UIApplication*)application
-    willContinueUserActivityWithType:(NSString*)userActivityType {
-  if (_appState.initStage <= InitStageSafeMode)
-    return NO;
-
-  // Enusre Chrome is fuilly started up in case it had launched to the
-  // background.
-  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
-
-  return
-      [UserActivityHandler willContinueUserActivityWithType:userActivityType];
-}
-
-- (BOOL)application:(UIApplication*)application
     continueUserActivity:(NSUserActivity*)userActivity
       restorationHandler:
           (void (^)(NSArray<id<UIUserActivityRestoring>>*))restorationHandler {
-  if (_appState.initStage <= InitStageSafeMode)
+  if (_appState.initStage <= InitStageSafeMode ||
+      _browserLauncher.browserInitializationStage <
+          INITIALIZATION_STAGE_FOREGROUND)
     return NO;
 
-  // Enusre Chrome is fuilly started up in case it had launched to the
-  // background.
-  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
-
   BOOL applicationIsActive =
       [application applicationState] == UIApplicationStateActive;
 
@@ -370,13 +355,11 @@
 - (void)application:(UIApplication*)application
     performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
                completionHandler:(void (^)(BOOL succeeded))completionHandler {
-  if (_appState.initStage <= InitStageSafeMode)
+  if (_appState.initStage <= InitStageSafeMode ||
+      _browserLauncher.browserInitializationStage <
+          INITIALIZATION_STAGE_FOREGROUND)
     return;
 
-  // Enusre Chrome is fuilly started up in case it had launched to the
-  // background.
-  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
-
   [UserActivityHandler
       performActionForShortcutItem:shortcutItem
                  completionHandler:completionHandler
@@ -395,16 +378,11 @@
 - (BOOL)application:(UIApplication*)application
             openURL:(NSURL*)url
             options:(NSDictionary<NSString*, id>*)options {
-  if (_appState.initStage <= InitStageSafeMode)
+  if (_appState.initStage <= InitStageSafeMode ||
+      _browserLauncher.browserInitializationStage <
+          INITIALIZATION_STAGE_FOREGROUND)
     return NO;
 
-  // The various URL handling mechanisms require that the application has
-  // fully started up; there are some cases (crbug.com/658420) where a
-  // launch via this method crashes because some services (specifically,
-  // CommandLine) aren't initialized yet. So: before anything further is
-  // done, make sure that Chrome is fully started up.
-  [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
-
   if (ios::GetChromeBrowserProvider()
           ->GetChromeIdentityService()
           ->HandleApplicationOpenURL(application, url, options)) {
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index aed69f78..9ae5d35 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -15,6 +15,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "components/breadcrumbs/core/breadcrumb_manager_keyed_service.h"
 #include "components/breadcrumbs/core/breadcrumb_persistent_storage_manager.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/component_updater/component_updater_service.h"
 #include "components/component_updater/crl_set_remover.h"
 #include "components/component_updater/installer_policies/autofill_states_component_installer.h"
@@ -57,7 +58,6 @@
 #import "ios/chrome/browser/browsing_data/sessions_storage_util.h"
 #include "ios/chrome/browser/chrome_paths.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_keyed_service_factory.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/crash_report/crash_helper.h"
 #include "ios/chrome/browser/crash_report/crash_keys_helper.h"
 #include "ios/chrome/browser/crash_report/crash_loop_detection_util.h"
@@ -514,7 +514,7 @@
   [self initializeBrowserState:chromeBrowserState];
   self.appState.mainBrowserState = chromeBrowserState;
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     [self startLoggingBreadcrumbs];
   }
 
@@ -696,7 +696,7 @@
   [_spotlightManager shutdown];
   _spotlightManager = nil;
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     if (self.appState.mainBrowserState->HasOffTheRecordChromeBrowserState()) {
       breadcrumbs::BreadcrumbManagerKeyedService* service =
           BreadcrumbManagerKeyedServiceFactory::GetForBrowserState(
diff --git a/ios/chrome/browser/BUILD.gn b/ios/chrome/browser/BUILD.gn
index 8f21db1..c0a49ca 100644
--- a/ios/chrome/browser/BUILD.gn
+++ b/ios/chrome/browser/BUILD.gn
@@ -189,6 +189,7 @@
     "//base",
     "//base/allocator:buildflags",
     "//components/breadcrumbs/core",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/component_updater",
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
@@ -222,7 +223,6 @@
     "//ios/chrome/browser/crash_report",
     "//ios/chrome/browser/crash_report/breadcrumbs",
     "//ios/chrome/browser/crash_report/breadcrumbs:application_breadcrumbs_logger",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/first_run",
     "//ios/chrome/browser/flags",
     "//ios/chrome/browser/gcm",
diff --git a/ios/chrome/browser/application_context_impl.mm b/ios/chrome/browser/application_context_impl.mm
index 695f8cb21..a50f867 100644
--- a/ios/chrome/browser/application_context_impl.mm
+++ b/ios/chrome/browser/application_context_impl.mm
@@ -24,6 +24,7 @@
 #include "base/time/default_tick_clock.h"
 #include "components/breadcrumbs/core/breadcrumb_manager.h"
 #include "components/breadcrumbs/core/breadcrumb_persistent_storage_manager.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/component_updater/component_updater_service.h"
 #include "components/component_updater/timer_update_scheduler.h"
 #include "components/gcm_driver/gcm_client_factory.h"
@@ -52,7 +53,6 @@
 #include "ios/chrome/browser/component_updater/ios_component_updater_configurator.h"
 #import "ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_persistent_storage_util.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/gcm/ios_chrome_gcm_profile_service_factory.h"
 #include "ios/chrome/browser/history/history_service_factory.h"
 #include "ios/chrome/browser/ios_chrome_io_thread.h"
@@ -173,7 +173,7 @@
                                    GetSharedURLLoaderFactory());
   }
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     breadcrumb_manager_ = std::make_unique<breadcrumbs::BreadcrumbManager>();
     application_breadcrumbs_logger_ =
         std::make_unique<ApplicationBreadcrumbsLogger>(
diff --git a/ios/chrome/browser/autofill/strike_database_factory.cc b/ios/chrome/browser/autofill/strike_database_factory.cc
index 2f0f4cb..58470bb8 100644
--- a/ios/chrome/browser/autofill/strike_database_factory.cc
+++ b/ios/chrome/browser/autofill/strike_database_factory.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "base/no_destructor.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
diff --git a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
index 9ce54806..c3a0879 100644
--- a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
+++ b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
@@ -21,8 +21,8 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/history/core/browser/history_service.h"
diff --git a/ios/chrome/browser/crash_report/BUILD.gn b/ios/chrome/browser/crash_report/BUILD.gn
index ac55a23a..cbc3292f 100644
--- a/ios/chrome/browser/crash_report/BUILD.gn
+++ b/ios/chrome/browser/crash_report/BUILD.gn
@@ -32,6 +32,7 @@
     "//base",
     "//build:branding_buildflags",
     "//components/breadcrumbs/core",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/breadcrumbs/ios",
     "//components/crash/core/app",
     "//components/crash/core/browser",
@@ -41,7 +42,6 @@
     "//ios/chrome/app:tests_hook",
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/common",
     "//ios/chrome/common/app_group",
     "//ios/chrome/common/crash_report",
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/BUILD.gn b/ios/chrome/browser/crash_report/breadcrumbs/BUILD.gn
index 663a9277..357958c 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/BUILD.gn
+++ b/ios/chrome/browser/crash_report/breadcrumbs/BUILD.gn
@@ -4,15 +4,6 @@
 
 import("//ios/build/config.gni")
 
-source_set("feature_flags") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  sources = [
-    "features.h",
-    "features.mm",
-  ]
-  deps = [ "//base" ]
-}
-
 source_set("breadcrumbs") {
   deps = [
     "//base",
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/features.h b/ios/chrome/browser/crash_report/breadcrumbs/features.h
deleted file mode 100644
index 60ed441e..0000000
--- a/ios/chrome/browser/crash_report/breadcrumbs/features.h
+++ /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.
-
-#ifndef IOS_CHROME_BROWSER_CRASH_REPORT_BREADCRUMBS_FEATURES_H_
-#define IOS_CHROME_BROWSER_CRASH_REPORT_BREADCRUMBS_FEATURES_H_
-
-#include "base/feature_list.h"
-
-// Feature flag to log breadcrumb events.
-extern const base::Feature kLogBreadcrumbs;
-
-#endif  // IOS_CHROME_BROWSER_CRASH_REPORT_BREADCRUMBS_FEATURES_H_
diff --git a/ios/chrome/browser/crash_report/features.cc b/ios/chrome/browser/crash_report/features.cc
index 680dd7d0..0600c2c 100644
--- a/ios/chrome/browser/crash_report/features.cc
+++ b/ios/chrome/browser/crash_report/features.cc
@@ -4,7 +4,7 @@
 
 #include "ios/chrome/browser/crash_report/features.h"
 
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
+#include "components/breadcrumbs/core/features.h"
 
 // Note the name here is "CrashpadIOSEnabler" and not "CrashpadIOS".  The former
 // is the name of the feature that eventually enables the latter synthetic flag
@@ -18,5 +18,5 @@
 
 bool EnableSyntheticCrashReportsForUte() {
   return base::FeatureList::IsEnabled(kSyntheticCrashReportsForUte) &&
-         base::FeatureList::IsEnabled(kLogBreadcrumbs);
+         base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs);
 }
diff --git a/ios/chrome/browser/first_run/first_run_metrics.h b/ios/chrome/browser/first_run/first_run_metrics.h
index d29d63f..7f175e7 100644
--- a/ios/chrome/browser/first_run/first_run_metrics.h
+++ b/ios/chrome/browser/first_run/first_run_metrics.h
@@ -81,8 +81,14 @@
   kStart,
   // The first run experience has completed.
   kComplete,
+  // Sync screen is shown.
+  kSyncScreenStart,
+  // Sync screen is closed with sync.
+  kSyncScreenCompletionWithSync,
+  // Sync screen is closed without sync.
+  kSyncScreenCompletionWithoutSync,
   // Max value of the first run experience stages.
-  kMaxValue = kComplete,
+  kMaxValue = kSyncScreenCompletionWithoutSync,
 };
 
 }  // namespace first_run
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn
index 99e4157..eac864ed 100644
--- a/ios/chrome/browser/flags/BUILD.gn
+++ b/ios/chrome/browser/flags/BUILD.gn
@@ -17,6 +17,7 @@
     "//base",
     "//components/autofill/core/common",
     "//components/autofill/ios/browser",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/dom_distiller/core",
     "//components/enterprise",
     "//components/feature_engagement/public",
@@ -49,7 +50,6 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser/browsing_data:feature_flags",
     "//ios/chrome/browser/crash_report",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/drag_and_drop",
     "//ios/chrome/browser/policy:feature_flags",
     "//ios/chrome/browser/screen_time:buildflags",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index ca7bcb9d..77e94e8 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -25,6 +25,7 @@
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/autofill/core/common/autofill_switches.h"
 #include "components/autofill/ios/browser/autofill_switches.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/content_settings/core/common/features.h"
 #include "components/dom_distiller/core/dom_distiller_switches.h"
 #include "components/enterprise/browser/enterprise_switches.h"
@@ -57,7 +58,6 @@
 #include "components/ukm/ios/features.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_features.h"
 #include "ios/chrome/browser/chrome_switches.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/crash_report/features.h"
 #include "ios/chrome/browser/flags/ios_chrome_flag_descriptions.h"
 #include "ios/chrome/browser/policy/policy_features.h"
@@ -474,7 +474,7 @@
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kCollectionsCardPresentationStyle)},
     {"ios-breadcrumbs", flag_descriptions::kLogBreadcrumbsName,
      flag_descriptions::kLogBreadcrumbsDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kLogBreadcrumbs)},
+     FEATURE_VALUE_TYPE(breadcrumbs::kLogBreadcrumbs)},
     {"ios-synthetic-crash-reports",
      flag_descriptions::kSyntheticCrashReportsForUteName,
      flag_descriptions::kSyntheticCrashReportsForUteDescription,
diff --git a/ios/chrome/browser/main/BUILD.gn b/ios/chrome/browser/main/BUILD.gn
index aa4db98..98caa81 100644
--- a/ios/chrome/browser/main/BUILD.gn
+++ b/ios/chrome/browser/main/BUILD.gn
@@ -44,11 +44,11 @@
 
   deps = [
     "//base",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/keyed_service/ios",
     "//ios/chrome/browser/app_launcher",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/crash_report/breadcrumbs",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/device_sharing",
     "//ios/chrome/browser/infobars/overlays/browser_agent:browser_agent_util",
     "//ios/chrome/browser/metrics:metrics_browser_agent",
diff --git a/ios/chrome/browser/main/browser_agent_util.mm b/ios/chrome/browser/main/browser_agent_util.mm
index b13580cf..0c9b474 100644
--- a/ios/chrome/browser/main/browser_agent_util.mm
+++ b/ios/chrome/browser/main/browser_agent_util.mm
@@ -5,10 +5,10 @@
 #import "ios/chrome/browser/main/browser_agent_util.h"
 
 #include "base/feature_list.h"
+#include "components/breadcrumbs/core/features.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_browser_agent.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
 #include "ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h"
 #import "ios/chrome/browser/metrics/tab_usage_recorder_browser_agent.h"
@@ -35,7 +35,7 @@
 #endif
 
 void AttachBrowserAgents(Browser* browser) {
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     BreadcrumbManagerBrowserAgent::CreateForBrowser(browser);
   }
   LiveTabContextBrowserAgent::CreateForBrowser(browser);
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index b69fe62..abbdb79 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -51,6 +51,7 @@
     ":chrome_browser_state_client",
     "//base",
     "//components/breadcrumbs/core",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/browser_sync",
     "//components/crash/core/common",
     "//components/keyed_service/core",
@@ -76,7 +77,6 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/crash_report",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/google",
     "//ios/chrome/browser/history",
     "//ios/chrome/browser/main:public",
diff --git a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
index 3bd585e..2819281 100644
--- a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
+++ b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
@@ -15,12 +15,12 @@
 #include "base/task/thread_pool.h"
 #include "base/version.h"
 #import "components/breadcrumbs/core/breadcrumb_persistent_storage_manager.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_service.h"
 #import "components/previous_session_info/previous_session_info.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/application_context.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/crash_report/crash_helper.h"
 #include "ios/chrome/browser/crash_report/features.h"
 #include "ios/chrome/browser/crash_report/main_thread_freeze_detector.h"
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index 947d919..75d0599 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -44,6 +44,7 @@
     ":tabs",
     "//base",
     "//components/autofill/ios/form_util",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/favicon/core",
     "//components/favicon/ios",
     "//components/history/core/browser",
@@ -60,7 +61,6 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/complex_tasks",
     "//ios/chrome/browser/crash_report/breadcrumbs",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/download",
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/find_in_page",
diff --git a/ios/chrome/browser/tabs/tab_helper_util.mm b/ios/chrome/browser/tabs/tab_helper_util.mm
index b7f4562..547d1a1 100644
--- a/ios/chrome/browser/tabs/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/tab_helper_util.mm
@@ -10,6 +10,7 @@
 
 #include "base/feature_list.h"
 #include "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/favicon/core/favicon_service.h"
 #import "components/favicon/ios/web_favicon_driver.h"
 #include "components/history/core/browser/top_sites.h"
@@ -26,7 +27,6 @@
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/complex_tasks/ios_task_tab_helper.h"
 #import "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_tab_helper.h"
-#import "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #import "ios/chrome/browser/download/ar_quick_look_tab_helper.h"
 #include "ios/chrome/browser/favicon/favicon_service_factory.h"
 #import "ios/chrome/browser/find_in_page/find_tab_helper.h"
@@ -134,7 +134,7 @@
     FontSizeTabHelper::CreateForWebState(web_state);
   }
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     BreadcrumbManagerTabHelper::CreateForWebState(web_state);
   }
 
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
index bbe0dd6c..2f531f7 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -15,8 +15,8 @@
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
 #include "components/autofill/core/browser/payments/legal_message_line.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl.h"
 #include "components/autofill/core/browser/ui/payments/card_name_fix_flow_controller_impl.h"
 #include "components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h"
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h b/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h
index 4bb573f..407d515a 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h
@@ -15,6 +15,8 @@
 class BookmarkNode;
 }
 
+class GURL;
+
 namespace web {
 class WebState;
 }
@@ -31,10 +33,16 @@
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
 
-// Presents the bookmark UI for a single bookmark.
+// Presents the bookmark UI for a single bookmark with |webState|'s current
+// committed URL and tab title.
 - (void)presentBookmarkEditorForWebState:(web::WebState*)webState
                      currentlyBookmarked:(BOOL)bookmarked;
 
+// Presents the bookmark UI for a single bookmark with |URL| and |title|.
+- (void)presentBookmarkEditorForURL:(const GURL&)URL
+                              title:(NSString*)title
+                currentlyBookmarked:(BOOL)bookmarked;
+
 // Presents the bookmarks browser modally.
 - (void)presentBookmarks;
 
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.mm
index d47cbbb..21ba42c5 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.mm
@@ -201,26 +201,33 @@
 
 - (void)presentBookmarkEditorForWebState:(web::WebState*)webState
                      currentlyBookmarked:(BOOL)bookmarked {
-  if (!self.bookmarkModel->loaded())
-    return;
   if (!webState)
     return;
 
-  GURL bookmarkedURL = webState->GetLastCommittedURL();
+  [self presentBookmarkEditorForURL:webState->GetLastCommittedURL()
+                              title:tab_util::GetTabTitle(webState)
+                currentlyBookmarked:bookmarked];
+}
+
+- (void)presentBookmarkEditorForURL:(const GURL&)URL
+                              title:(NSString*)title
+                currentlyBookmarked:(BOOL)bookmarked {
+  if (!self.bookmarkModel->loaded())
+    return;
 
   if (bookmarked) {
-    [self presentBookmarkEditorForBookmarkedURL:bookmarkedURL];
+    [self presentBookmarkEditorForBookmarkedURL:URL];
   } else {
     __weak BookmarkInteractionController* weakSelf = self;
+    // Copy of |URL| to be captured in block.
+    GURL bookmarkedURL(URL);
     void (^editAction)() = ^{
       [weakSelf presentBookmarkEditorForBookmarkedURL:bookmarkedURL];
     };
     [self.handler
-        showSnackbarMessage:[self.mediator
-                                addBookmarkWithTitle:tab_util::GetTabTitle(
-                                                         webState)
-                                                 URL:bookmarkedURL
-                                          editAction:editAction]];
+        showSnackbarMessage:[self.mediator addBookmarkWithTitle:title
+                                                            URL:bookmarkedURL
+                                                     editAction:editAction]];
   }
 }
 
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 c3d5a37..b288c202 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -136,7 +136,6 @@
 #import "ios/chrome/browser/ui/toolbar/accessory/toolbar_accessory_presenter.h"
 #import "ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.h"
 #import "ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.h"
-#import "ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h"
 #import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui.h"
 #import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui_broadcasting_util.h"
 #import "ios/chrome/browser/ui/toolbar/primary_toolbar_coordinator.h"
@@ -356,7 +355,6 @@
                                      SigninPresenter,
                                      SnapshotGeneratorDelegate,
                                      TabStripPresentation,
-                                     ToolbarHeightProviderForFullscreen,
                                      UIGestureRecognizerDelegate,
                                      URLLoadingObserver,
                                      ViewRevealingAnimatee,
@@ -428,8 +426,8 @@
 
   ToolbarCoordinatorAdaptor* _toolbarCoordinatorAdaptor;
 
-  // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
-  LegacyToolbarUIUpdater* _toolbarUIUpdater;
+  // Toolbar state that broadcasts changes to min and max heights.
+  ToolbarUIState* _toolbarUIState;
 
   // The main content UI updater for the content displayed by this BVC.
   MainContentUIStateUpdater* _mainContentUIUpdater;
@@ -936,12 +934,8 @@
 
   ChromeBroadcaster* broadcaster = self.fullscreenController->broadcaster();
   if (_broadcasting) {
-    _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
-        initWithToolbarUI:[[ToolbarUIState alloc] init]
-             toolbarOwner:self
-             webStateList:self.browser->GetWebStateList()];
-    [_toolbarUIUpdater startUpdating];
-    StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
+    _toolbarUIState = [[ToolbarUIState alloc] init];
+    StartBroadcastingToolbarUI(_toolbarUIState, broadcaster);
 
     _mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
         initWithState:[[MainContentUIState alloc] init]];
@@ -956,9 +950,8 @@
   } else {
     StopBroadcastingToolbarUI(broadcaster);
     StopBroadcastingMainContentUI(broadcaster);
-    [_toolbarUIUpdater stopUpdating];
-    _toolbarUIUpdater = nil;
     _mainContentUIUpdater = nil;
+    _toolbarUIState = nil;
     [_webMainContentUIForwarder disconnect];
     _webMainContentUIForwarder = nil;
 
@@ -1531,7 +1524,7 @@
   [super viewDidAppear:animated];
   self.viewVisible = YES;
   [self updateBroadcastState];
-  [_toolbarUIUpdater updateState];
+  [self updateToolbarState];
   [self.infobarContainerCoordinator baseViewDidAppear];
 
   // |viewDidAppear| can be called after |browserState| is destroyed. Since
@@ -1609,8 +1602,7 @@
     [self.browserViewHiderCoordinator stop];
     self.browserViewHiderCoordinator = nil;
     self.toolbarInterface = nil;
-    [_toolbarUIUpdater stopUpdating];
-    _toolbarUIUpdater = nil;
+    _toolbarUIState = nil;
     _locationBarModelDelegate = nil;
     _locationBarModel = nil;
     self.helper = nil;
@@ -1656,7 +1648,7 @@
     self.currentWebState->GetWebViewProxy().contentInset = contentPadding;
   }
 
-  [_toolbarUIUpdater updateState];
+  [self updateToolbarState];
 
   // If the device's size class has changed from RegularXRegular to another and
   // vice-versa, the find bar should switch between regular mode and compact
@@ -1721,9 +1713,9 @@
 }
 
 - (void)animateTransition {
-  // Force updates of the toolbar updater as the toolbar height might
+  // Force updates of the toolbar state as the toolbar height might
   // change on rotation.
-  [_toolbarUIUpdater updateState];
+  [self updateToolbarState];
   // Resize horizontal viewport if Smooth Scrolling is on.
   if (fullscreen::features::ShouldUseSmoothScrolling()) {
     self.fullscreenController->ResizeHorizontalViewport();
@@ -2475,7 +2467,7 @@
     }
     [self viewForWebState:webState].frame = webStateViewFrame;
 
-    [_toolbarUIUpdater updateState];
+    [self updateToolbarState];
     NewTabPageTabHelper* NTPHelper =
         NewTabPageTabHelper::FromWebState(webState);
     if (NTPHelper && NTPHelper->IsActive()) {
@@ -2609,7 +2601,7 @@
   // NTP is laid out only in the visible part of the screen.
   UIEdgeInsets viewportInsets = UIEdgeInsetsZero;
   if (!IsRegularXRegularSizeClass(self)) {
-    viewportInsets.bottom = [self bottomToolbarHeight];
+    viewportInsets.bottom = [self secondaryToolbarHeightWithInset];
   }
 
   // Add toolbar margin to the frame for every scenario except compact-width
@@ -3995,7 +3987,6 @@
 }
 
 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
-  [_toolbarUIUpdater updateState];
   if ([self canShowTabStrip]) {
     UIUserInterfaceSizeClass sizeClass =
         self.view.window.traitCollection.horizontalSizeClass;
@@ -4114,24 +4105,6 @@
   return self.fullscreenController;
 }
 
-#pragma mark - ToolbarHeightProviderForFullscreen
-
-- (CGFloat)collapsedTopToolbarHeight {
-  return self.view.safeAreaInsets.top +
-         ToolbarCollapsedHeight(
-             self.traitCollection.preferredContentSizeCategory);
-}
-
-- (CGFloat)expandedTopToolbarHeight {
-  return [self primaryToolbarHeightWithInset] +
-         ([self canShowTabStrip] ? self.tabStripView.frame.size.height : 0.0) +
-         self.headerOffset;
-}
-
-- (CGFloat)bottomToolbarHeight {
-  return [self secondaryToolbarHeightWithInset];
-}
-
 #pragma mark - FullscreenUIElement methods
 
 - (void)updateForFullscreenProgress:(CGFloat)progress {
@@ -4190,6 +4163,30 @@
 
 #pragma mark - FullscreenUIElement helpers
 
+// The minimum amount by which the top toolbar overlaps the browser content
+// area.
+- (CGFloat)collapsedTopToolbarHeight {
+  return self.view.safeAreaInsets.top +
+         ToolbarCollapsedHeight(
+             self.traitCollection.preferredContentSizeCategory);
+}
+
+// The maximum amount by which the top toolbar overlaps the browser content
+// area.
+- (CGFloat)expandedTopToolbarHeight {
+  return [self primaryToolbarHeightWithInset] +
+         ([self canShowTabStrip] ? self.tabStripView.frame.size.height : 0.0) +
+         self.headerOffset;
+}
+
+// Updates the ToolbarUIState, which broadcasts any changes to registered
+// listeners.
+- (void)updateToolbarState {
+  _toolbarUIState.collapsedHeight = [self collapsedTopToolbarHeight];
+  _toolbarUIState.expandedHeight = [self expandedTopToolbarHeight];
+  _toolbarUIState.bottomToolbarHeight = [self secondaryToolbarHeightWithInset];
+}
+
 // Returns the height difference between the fully expanded and fully collapsed
 // primary toolbar.
 - (CGFloat)primaryToolbarHeightDelta {
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
index efe208f..80d3fa96 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
@@ -662,8 +662,10 @@
   NSString* footerTitle = sectionInfo.footerTitle;
 
   __weak ContentSuggestionsCollectionUpdater* weakSelf = self;
-  if (footerTitle && ![self.collectionViewController.collectionViewModel
-                         footerForSectionWithIdentifier:sectionIdentifier]) {
+  if (footerTitle &&
+      ![self.collectionViewController.collectionViewModel
+          footerForSectionWithIdentifier:sectionIdentifier] &&
+      !IsDiscoverFeedEnabled()) {
     ContentSuggestionsFooterItem* footer = [[ContentSuggestionsFooterItem alloc]
         initWithType:ItemTypeFooter
                title:sectionInfo.footerTitle
@@ -697,13 +699,18 @@
 
   NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier];
   if ([self isDiscoverSection:section]) {
-    [model setHeader:[self headerForSectionInfo:sectionInfo]
-        forSectionWithIdentifier:sectionIdentifier];
+    CollectionViewItem* discoverSectionHeader =
+        [self headerForSectionInfo:sectionInfo];
+    // TODO(crbug.com/1145106): Potential fix for crash where cellClass is nil.
+    if ([discoverSectionHeader cellClass]) {
+      [model setHeader:discoverSectionHeader
+          forSectionWithIdentifier:sectionIdentifier];
+    }
     return;
   }
 
   if (![model headerForSectionWithIdentifier:sectionIdentifier] &&
-      sectionInfo.title) {
+      sectionInfo.title && !IsDiscoverFeedEnabled()) {
     DCHECK(IsFromContentSuggestionsService(sectionIdentifier));
     if ([self.sectionIdentifiersFromContentSuggestions
             containsObject:@(sectionIdentifier)]) {
diff --git a/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm b/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
index 0ca1c3ad..380f8ab 100644
--- a/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
+++ b/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
@@ -18,18 +18,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-
-// Enum actions for the IOS.DefaultBrowserFullscreenPromo UMA metric.
-enum IOSDefaultBrowserFullscreenPromoAction {
-  ACTION_BUTTON = 0,
-  CANCEL = 1,
-  REMIND_ME_LATER = 2,
-  kMaxValue = REMIND_ME_LATER,
-};
-
-}  // namespace
-
 @interface DefaultBrowserPromoCoordinator () <
     ConfirmationAlertActionHandler,
     UIAdaptivePresentationControllerDelegate>
diff --git a/ios/chrome/browser/ui/default_promo/default_browser_utils.h b/ios/chrome/browser/ui/default_promo/default_browser_utils.h
index b40547d..af54abd 100644
--- a/ios/chrome/browser/ui/default_promo/default_browser_utils.h
+++ b/ios/chrome/browser/ui/default_promo/default_browser_utils.h
@@ -16,6 +16,19 @@
   DefaultPromoTypeAllTabs = 3
 };
 
+namespace {
+
+// Enum actions for the IOS.DefaultBrowserFullscreenPromo* UMA metrics. Entries
+// should not be renumbered and numeric values should never be reused.
+enum IOSDefaultBrowserFullscreenPromoAction {
+  ACTION_BUTTON = 0,
+  CANCEL = 1,
+  REMIND_ME_LATER = 2,
+  kMaxValue = REMIND_ME_LATER,
+};
+
+}  // namespace
+
 // UserDefaults key that saves the last time an HTTP(S) link was sent and opened
 // by the app.
 extern NSString* const kLastHTTPURLOpenTime;
diff --git a/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm b/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
index 87ea098..f365dc2d 100644
--- a/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
+++ b/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
@@ -131,7 +131,8 @@
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss",
                           DefaultPromoTypeForUMA(_promoType));
-
+  [self logDefaultBrowserFullscreenPromoHistogramForAction:
+            IOSDefaultBrowserFullscreenPromoAction::CANCEL];
   // This ensures that a modal swipe dismiss will also be logged.
   LogUserInteractionWithTailoredFullscreenPromo();
 }
@@ -148,8 +149,10 @@
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Accepted"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Accepted",
                           DefaultPromoTypeForUMA(_promoType));
-
+  [self logDefaultBrowserFullscreenPromoHistogramForAction:
+            IOSDefaultBrowserFullscreenPromoAction::ACTION_BUTTON];
   LogUserInteractionWithTailoredFullscreenPromo();
+
   [[UIApplication sharedApplication]
                 openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]
                 options:{}
@@ -163,8 +166,10 @@
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss",
                           DefaultPromoTypeForUMA(_promoType));
-
+  [self logDefaultBrowserFullscreenPromoHistogramForAction:
+            IOSDefaultBrowserFullscreenPromoAction::CANCEL];
   LogUserInteractionWithTailoredFullscreenPromo();
+
   [self.handler hidePromo];
 }
 
@@ -185,4 +190,27 @@
                  completion:nil];
 }
 
+#pragma mark - Metrics Helpers
+
+- (void)logDefaultBrowserFullscreenPromoHistogramForAction:
+    (IOSDefaultBrowserFullscreenPromoAction)action {
+  switch (self.promoType) {
+    case DefaultPromoTypeAllTabs:
+      UmaHistogramEnumeration(
+          "IOS.DefaultBrowserFullscreenTailoredPromoAllTabs", action);
+      break;
+    case DefaultPromoTypeMadeForIOS:
+      UmaHistogramEnumeration(
+          "IOS.DefaultBrowserFullscreenTailoredPromoMadeForIOS", action);
+      break;
+    case DefaultPromoTypeStaySafe:
+      UmaHistogramEnumeration(
+          "IOS.DefaultBrowserFullscreenTailoredPromoStaySafe", action);
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
index 990bf3a..6321bf2 100644
--- a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
@@ -7,6 +7,7 @@
 #import <UIKit/UIKit.h>
 
 #import "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "ios/chrome/browser/first_run/first_run_metrics.h"
 #import "ios/chrome/browser/ui/first_run/first_run_screen_delegate.h"
 #import "ios/chrome/browser/ui/first_run/first_run_screen_provider.h"
@@ -83,10 +84,10 @@
 #pragma mark - Helper
 
 // Presents the screen of certain |type|.
-- (void)presentScreen:(NSNumber*)type {
+- (void)presentScreen:(FirstRunScreenType)type {
   // If no more screen need to be present, call delegate to stop presenting
   // screens.
-  if ([type isEqualToNumber:@(kFirstRunCompleted)]) {
+  if (type == kFirstRunCompleted) {
     [self.delegate willFinishPresentingScreens];
     return;
   }
@@ -95,8 +96,9 @@
 }
 
 // Creates a screen coordinator according to |type|.
-- (ChromeCoordinator*)createChildCoordinatorWithScreenType:(NSNumber*)type {
-  switch ([type integerValue]) {
+- (ChromeCoordinator*)createChildCoordinatorWithScreenType:
+    (FirstRunScreenType)type {
+  switch (type) {
     case kWelcomeAndConsent:
       return [[WelcomeScreenCoordinator alloc]
           initWithBaseNavigationController:self.navigationController
@@ -115,6 +117,9 @@
     case kDefaultBrowserPromo:
       // TODO (crbug.com/1189807): Create the default browser screen.
       return nil;
+    case kFirstRunCompleted:
+      NOTREACHED() << "Reaches kFirstRunCompleted unexpectedly.";
+      break;
   }
   return nil;
 }
diff --git a/ios/chrome/browser/ui/first_run/first_run_screen_provider.h b/ios/chrome/browser/ui/first_run/first_run_screen_provider.h
index b04cd40..1378b00f 100644
--- a/ios/chrome/browser/ui/first_run/first_run_screen_provider.h
+++ b/ios/chrome/browser/ui/first_run/first_run_screen_provider.h
@@ -7,13 +7,15 @@
 
 #import <Foundation/Foundation.h>
 
+#import "ios/chrome/browser/ui/first_run/first_run_screen_type.h"
+
 // The class that provides a list of first run screens.
 @interface FirstRunScreenProvider : NSObject
 
 - (instancetype)init NS_DESIGNATED_INITIALIZER;
 
 // Returns the screen type of next screen.
-- (NSNumber*)nextScreenType;
+- (FirstRunScreenType)nextScreenType;
 
 @end
 
diff --git a/ios/chrome/browser/ui/first_run/first_run_screen_provider.mm b/ios/chrome/browser/ui/first_run/first_run_screen_provider.mm
index 87154db..b2b4ea5 100644
--- a/ios/chrome/browser/ui/first_run/first_run_screen_provider.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_screen_provider.mm
@@ -5,15 +5,18 @@
 #import "ios/chrome/browser/ui/first_run/first_run_screen_provider.h"
 
 #include "base/check.h"
-#import "ios/chrome/browser/ui/first_run/first_run_screen_type.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+// Screen array.
+FirstRunScreenType screens[] = {};
+}
+
 @interface FirstRunScreenProvider ()
 
-@property(nonatomic, strong) NSArray* screens;
 @property(nonatomic) int index;
 
 @end
@@ -23,22 +26,26 @@
 - (instancetype)init {
   self = [super init];
   if (self) {
-    // Set up screens.
-    // Hardcoded default screen order for class initiation.
-    // TODO(crbug.com/1195198): Add logic to generate a custimizeed screen
-    // order.
-    _screens =
-        @[ @(kWelcomeAndConsent), @(kSignIn), @(kSync), @(kFirstRunCompleted) ];
+    SetupScreens();
     _index = -1;
   }
   return self;
 }
 
-- (NSNumber*)nextScreenType {
-  DCHECK(self.screens);
-  DCHECK(self.index == -1 ||
-         ![self.screens[self.index] isEqualToNumber:@(kFirstRunCompleted)]);
-  return [self.screens objectAtIndex:++self.index];
+- (FirstRunScreenType)nextScreenType {
+  DCHECK(screens);
+  DCHECK(self.index == -1 || screens[self.index] != kFirstRunCompleted);
+  return screens[++self.index];
+}
+
+#pragma mark - Private
+void SetupScreens() {
+  // TODO(crbug.com/1195198): Add logic to generate a custimizeed screen
+  // order.
+  screens[0] = kWelcomeAndConsent;
+  screens[1] = kSignIn;
+  screens[2] = kSync;
+  screens[3] = kFirstRunCompleted;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/first_run/first_run_screen_type.h b/ios/chrome/browser/ui/first_run/first_run_screen_type.h
index 5f2e71d..827e419 100644
--- a/ios/chrome/browser/ui/first_run/first_run_screen_type.h
+++ b/ios/chrome/browser/ui/first_run/first_run_screen_type.h
@@ -11,7 +11,6 @@
   kSignIn,
   kSync,
   kDefaultBrowserPromo,
-  kLocation,
   // It isn't a screen, but a signal that no more screen should be
   // presented.
   kFirstRunCompleted,
diff --git a/ios/chrome/browser/ui/first_run/resources/BUILD.gn b/ios/chrome/browser/ui/first_run/resources/BUILD.gn
index 30065281..939d4a6 100644
--- a/ios/chrome/browser/ui/first_run/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/resources/BUILD.gn
@@ -49,6 +49,8 @@
 imageset("sync_screen_banner") {
   sources = [
     "sync_screen_banner.imageset/Contents.json",
+    "sync_screen_banner.imageset/sync_screen_banner_dark@2x.png",
+    "sync_screen_banner.imageset/sync_screen_banner_dark@3x.png",
     "sync_screen_banner.imageset/sync_screen_banner_light@2x.png",
     "sync_screen_banner.imageset/sync_screen_banner_light@3x.png",
   ]
diff --git a/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/Contents.json b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/Contents.json
index b92ca041..1cafceb 100644
--- a/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/Contents.json
+++ b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/Contents.json
@@ -1,13 +1,35 @@
 {
   "images" : [
     {
-      "idiom" : "iphone",
       "filename" : "sync_screen_banner_light@2x.png",
+      "idiom" : "universal",
       "scale" : "2x"
     },
     {
-      "idiom" : "iphone",
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "filename" : "sync_screen_banner_dark@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
       "filename" : "sync_screen_banner_light@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "filename" : "sync_screen_banner_dark@3x.png",
+      "idiom" : "universal",
       "scale" : "3x"
     }
   ],
diff --git a/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@2x.png b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@2x.png
new file mode 100644
index 0000000..489fa07d
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@3x.png b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@3x.png
new file mode 100644
index 0000000..5d9f667f
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/resources/sync_screen_banner.imageset/sync_screen_banner_dark@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/first_run/sync/BUILD.gn b/ios/chrome/browser/ui/first_run/sync/BUILD.gn
index a145b63..797d481 100644
--- a/ios/chrome/browser/ui/first_run/sync/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/sync/BUILD.gn
@@ -13,9 +13,17 @@
   deps = [
     ":sync_ui",
     "//base:base",
+    "//components/consent_auditor",
+    "//components/unified_consent",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/first_run",
     "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/sync",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/first_run:screen_delegate",
+    "//ios/chrome/browser/unified_consent",
+    "//ios/public/provider/chrome/browser/signin",
   ]
   frameworks = [ "UIKit.framework" ]
 }
diff --git a/ios/chrome/browser/ui/first_run/sync/DEPS b/ios/chrome/browser/ui/first_run/sync/DEPS
new file mode 100644
index 0000000..b59fd46
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/sync/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/consent_auditor",
+]
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
index bbe2ab2..e5fabbc6 100644
--- a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
@@ -4,8 +4,17 @@
 
 #import "ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.h"
 
+#import "base/metrics/histogram_functions.h"
+#include "ios/chrome/browser/first_run/first_run_metrics.h"
 #include "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/signin/identity_manager_factory.h"
+#import "ios/chrome/browser/sync/consent_auditor_factory.h"
+#import "ios/chrome/browser/sync/sync_setup_service_factory.h"
+#import "ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h"
 #import "ios/chrome/browser/ui/first_run/sync/sync_screen_view_controller.h"
+#import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -15,6 +24,8 @@
 
 // Sync screen view controller.
 @property(nonatomic, strong) SyncScreenViewController* viewController;
+// Sync screen mediator.
+@property(nonatomic, strong) SyncScreenMediator* mediator;
 
 @property(nonatomic, weak) id<FirstRunScreenDelegate> delegate;
 
@@ -43,23 +54,53 @@
   // if not:
   // [self.delegate willFinishPresenting]
   // if yes:
+  // DCHECK if an user identity is existed.
+  ChromeBrowserState* browserState = self.browser->GetBrowserState();
+  AuthenticationService* authenticationService =
+      AuthenticationServiceFactory::GetForBrowserState(browserState);
+  DCHECK(authenticationService->GetAuthenticatedIdentity());
+
   self.viewController = [[SyncScreenViewController alloc] init];
   self.viewController.delegate = self;
+
+  // Setup mediator.
+  self.mediator = [[SyncScreenMediator alloc]
+      initWithAuthenticationService:authenticationService
+                    identityManager:IdentityManagerFactory::GetForBrowserState(
+                                        browserState)
+                     consentAuditor:ConsentAuditorFactory::GetForBrowserState(
+                                        browserState)
+              unifiedConsentService:UnifiedConsentServiceFactory::
+                                        GetForBrowserState(browserState)
+                   syncSetupService:SyncSetupServiceFactory::GetForBrowserState(
+                                        browserState)];
+
+  base::UmaHistogramEnumeration("FirstRun.Stage", first_run::kSyncScreenStart);
+
   BOOL animated = self.baseNavigationController.topViewController != nil;
   [self.baseNavigationController setViewControllers:@[ self.viewController ]
                                            animated:animated];
 }
 
 - (void)stop {
+  // TODO(crbug.com/1189840): Display the sync errors infobar.
   self.delegate = nil;
   self.viewController = nil;
+  self.mediator = nil;
 }
 
 #pragma mark - SyncScreenViewControllerDelegate
 
 - (void)didTapPrimaryActionButton {
-  // TODO(crbug.com/1189840): record sync status.
+  base::UmaHistogramEnumeration("FirstRun.Stage",
+                                first_run::kSyncScreenCompletionWithSync);
+  [self.mediator startSync];
+  [self.delegate willFinishPresenting];
+}
 
+- (void)didTapSecondaryActionButton {
+  base::UmaHistogramEnumeration("FirstRun.Stage",
+                                first_run::kSyncScreenCompletionWithoutSync);
   [self.delegate willFinishPresenting];
 }
 
diff --git a/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h b/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h
index 40ef052e..3cc57389 100644
--- a/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h
+++ b/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h
@@ -7,9 +7,44 @@
 
 #import <Foundation/Foundation.h>
 
+class AuthenticationService;
+class SyncSetupService;
+
+namespace consent_auditor {
+class ConsentAuditor;
+}
+
+namespace signin {
+class IdentityManager;
+}
+
+namespace unified_consent {
+class UnifiedConsentService;
+}
+
 // Mediator that handles the sync operation.
 @interface SyncScreenMediator : NSObject
 
+- (instancetype)init NS_UNAVAILABLE;
+
+// Inits the mediator with
+// |authenticationService| provides the authentication library.
+// |identityManager| gives access to information of users Google identity.
+// |consentAuditor| to record the content.
+// |syncSetupService| helps triggering the sync flow.
+- (instancetype)
+    initWithAuthenticationService:(AuthenticationService*)authenticationService
+                  identityManager:(signin::IdentityManager*)identityManager
+                   consentAuditor:
+                       (consent_auditor::ConsentAuditor*)consentAuditor
+            unifiedConsentService:
+                (unified_consent::UnifiedConsentService*)unifiedConsentService
+                 syncSetupService:(SyncSetupService*)syncSetupService
+    NS_DESIGNATED_INITIALIZER;
+
+// Starts the sync engine.
+- (void)startSync;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_FIRST_RUN_SYNC_SYNC_SCREEN_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.mm b/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.mm
index 198f793..d6b75a4 100644
--- a/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.mm
+++ b/ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.mm
@@ -4,10 +4,75 @@
 
 #import "ios/chrome/browser/ui/first_run/sync/sync_screen_mediator.h"
 
+#import "base/strings/sys_string_conversions.h"
+#import "components/consent_auditor/consent_auditor.h"
+#import "components/unified_consent/unified_consent_service.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/identity_manager_factory.h"
+#import "ios/chrome/browser/sync/sync_setup_service.h"
+#include "ios/chrome/grit/ios_strings.h"
+#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
+
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+@interface SyncScreenMediator ()
+
+// Manager for user's Google identities.
+@property(nonatomic, assign) signin::IdentityManager* identityManager;
+// Auditor for user consent.
+@property(nonatomic, assign) consent_auditor::ConsentAuditor* consentAuditor;
+// Chrome interface to the iOS shared authentication library.
+@property(nonatomic, assign) AuthenticationService* authenticationService;
+// Manager for user consent.
+@property(nonatomic, assign)
+    unified_consent::UnifiedConsentService* unifiedConsentService;
+// Service that allows for configuring sync.
+@property(nonatomic, assign) SyncSetupService* syncSetupService;
+
+@end
+
 @implementation SyncScreenMediator
 
+- (instancetype)
+    initWithAuthenticationService:(AuthenticationService*)authenticationService
+                  identityManager:(signin::IdentityManager*)identityManager
+                   consentAuditor:
+                       (consent_auditor::ConsentAuditor*)consentAuditor
+            unifiedConsentService:
+                (unified_consent::UnifiedConsentService*)unifiedConsentService
+                 syncSetupService:(SyncSetupService*)syncSetupService {
+  self = [super init];
+  if (self) {
+    _identityManager = identityManager;
+    _consentAuditor = consentAuditor;
+    _authenticationService = authenticationService;
+    _unifiedConsentService = unifiedConsentService;
+    _syncSetupService = syncSetupService;
+  }
+  return self;
+}
+
+- (void)startSync {
+  sync_pb::UserConsentTypes::SyncConsent syncConsent;
+  syncConsent.set_status(sync_pb::UserConsentTypes::ConsentStatus::
+                             UserConsentTypes_ConsentStatus_GIVEN);
+
+  syncConsent.set_confirmation_grd_id(
+      IDS_IOS_FIRST_RUN_SYNC_SCREEN_PRIMARY_ACTION);
+
+  ChromeIdentity* identity =
+      self.authenticationService->GetAuthenticatedIdentity();
+  DCHECK(identity);
+
+  CoreAccountId coreAccountId = self.identityManager->PickAccountIdForAccount(
+      base::SysNSStringToUTF8([identity gaiaID]),
+      base::SysNSStringToUTF8([identity userEmail]));
+  self.consentAuditor->RecordSyncConsent(coreAccountId, syncConsent);
+  self.authenticationService->GrantSyncConsent(identity);
+
+  self.syncSetupService->CommitSyncChanges();
+}
+
 @end
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index 8bd91595..0538d60 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -103,6 +103,7 @@
     "//base",
     "//base/ios",
     "//components/breadcrumbs/core",
+    "//components/breadcrumbs/core:feature_flags",
     "//components/infobars/core",
     "//components/previous_session_info",
     "//components/signin/public/identity_manager",
@@ -123,7 +124,6 @@
     "//ios/chrome/browser/crash_report",
     "//ios/chrome/browser/crash_report:crash_report_internal",
     "//ios/chrome/browser/crash_report/breadcrumbs",
-    "//ios/chrome/browser/crash_report/breadcrumbs:feature_flags",
     "//ios/chrome/browser/first_run",
     "//ios/chrome/browser/geolocation",
     "//ios/chrome/browser/infobars",
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index d1c84da0..b7fc50ca 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -97,7 +97,7 @@
 
 @interface BrowserViewWrangler () {
   ChromeBrowserState* _browserState;
-  SceneState* _sceneState;
+  __weak SceneState* _sceneState;
   __weak id<ApplicationCommands> _applicationCommandEndpoint;
   __weak id<BrowsingDataCommands> _browsingDataCommandEndpoint;
   BOOL _isShutdown;
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index df96950..2688c31 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -18,6 +18,7 @@
 #include "base/task/post_task.h"
 #include "components/breadcrumbs/core/breadcrumb_manager_keyed_service.h"
 #include "components/breadcrumbs/core/breadcrumb_persistent_storage_manager.h"
+#include "components/breadcrumbs/core/features.h"
 #include "components/infobars/core/infobar_manager.h"
 #include "components/prefs/pref_service.h"
 #import "components/previous_session_info/previous_session_info.h"
@@ -43,7 +44,6 @@
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_keyed_service_factory.h"
-#include "ios/chrome/browser/crash_report/breadcrumbs/features.h"
 #include "ios/chrome/browser/crash_report/crash_keys_helper.h"
 #include "ios/chrome/browser/crash_report/crash_report_helper.h"
 #import "ios/chrome/browser/crash_report/crash_restore_helper.h"
@@ -3004,7 +3004,7 @@
 
   breadcrumbs::BreadcrumbPersistentStorageManager* persistentStorageManager =
       nullptr;
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     breadcrumbs::BreadcrumbManagerKeyedService* service =
         BreadcrumbManagerKeyedServiceFactory::GetForBrowserState(
             mainBrowserState->GetOffTheRecordChromeBrowserState());
@@ -3030,7 +3030,7 @@
     [sceneController incognitoBrowserStateCreated];
   }
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     breadcrumbs::BreadcrumbManagerKeyedService* service =
         BreadcrumbManagerKeyedServiceFactory::GetForBrowserState(
             mainBrowserState->GetOffTheRecordChromeBrowserState());
@@ -3052,7 +3052,7 @@
   // will be destroyed.
   self.mainCoordinator.incognitoBrowser = nil;
 
-  if (base::FeatureList::IsEnabled(kLogBreadcrumbs)) {
+  if (base::FeatureList::IsEnabled(breadcrumbs::kLogBreadcrumbs)) {
     BreadcrumbManagerBrowserAgent::FromBrowser(self.incognitoInterface.browser)
         ->SetLoggingEnabled(false);
   }
diff --git a/ios/chrome/browser/ui/menu/tab_context_menu_delegate.h b/ios/chrome/browser/ui/menu/tab_context_menu_delegate.h
index 883712ac..e77ec980 100644
--- a/ios/chrome/browser/ui/menu/tab_context_menu_delegate.h
+++ b/ios/chrome/browser/ui/menu/tab_context_menu_delegate.h
@@ -34,6 +34,9 @@
 // Tells the delegate to add |URL| and |title| to the reading list.
 - (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
 
+// Tells the delegate to create a bookmark for |URL| with |title|.
+- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title;
+
 // Tells the delegate to close the tab with the item identifier |identifier|.
 - (void)closeTabWithIdentifier:(NSString*)identifier incognito:(BOOL)incognito;
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
index a021a743..96a11c3 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
@@ -20,12 +20,14 @@
     ":tab_grid_ui",
     "grid:grid_ui",
     "//base",
+    "//components/bookmarks/browser",
     "//components/favicon/ios",
     "//components/prefs",
     "//components/sessions",
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/bookmarks",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/drag_and_drop",
     "//ios/chrome/browser/main",
@@ -39,6 +41,7 @@
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/activity_services",
     "//ios/chrome/browser/ui/alert_coordinator",
+    "//ios/chrome/browser/ui/bookmarks",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/default_promo",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
index c4477302..a64e91b9 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
@@ -63,6 +63,7 @@
     "//ios/chrome/browser/ui/incognito_reauth:incognito_reauth_ui",
     "//ios/chrome/browser/ui/menu",
     "//ios/chrome/browser/ui/menu:context_menu_delegate",
+    "//ios/chrome/browser/ui/ntp:util",
     "//ios/chrome/browser/ui/tab_switcher",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid:features",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/transitions",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
index ea3c25a..0fea664c 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
@@ -10,6 +10,7 @@
 #import "ios/chrome/browser/ui/menu/action_factory.h"
 #import "ios/chrome/browser/ui/menu/menu_histograms.h"
 #import "ios/chrome/browser/ui/menu/tab_context_menu_delegate.h"
+#import "ios/chrome/browser/ui/ntp/ntp_util.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_menu_actions_data_source.h"
@@ -70,19 +71,32 @@
         NSMutableArray<UIMenuElement*>* menuElements =
             [[NSMutableArray alloc] init];
 
-        [menuElements addObject:[actionFactory actionToShareWithBlock:^{
-                        [weakSelf.contextMenuDelegate shareURL:item.URL
-                                                         title:item.title
-                                                      fromView:gridCell];
-                      }]];
-        if ([weakSelf.contextMenuDelegate
-                respondsToSelector:@selector(addToReadingListURL:title:)]) {
-          [menuElements
-              addObject:[actionFactory actionToAddToReadingListWithBlock:^{
-                [weakSelf.contextMenuDelegate addToReadingListURL:item.URL
-                                                            title:item.title];
-              }]];
+        if (!IsURLNewTabPage(item.URL)) {
+          [menuElements addObject:[actionFactory actionToShareWithBlock:^{
+                          [weakSelf.contextMenuDelegate shareURL:item.URL
+                                                           title:item.title
+                                                        fromView:gridCell];
+                        }]];
+
+          if ([weakSelf.contextMenuDelegate
+                  respondsToSelector:@selector(addToReadingListURL:title:)]) {
+            [menuElements
+                addObject:[actionFactory actionToAddToReadingListWithBlock:^{
+                  [weakSelf.contextMenuDelegate addToReadingListURL:item.URL
+                                                              title:item.title];
+                }]];
+          }
+
+          if ([weakSelf.contextMenuDelegate
+                  respondsToSelector:@selector(bookmarkURL:title:)]) {
+            [menuElements addObject:[actionFactory actionToBookmarkWithBlock:^{
+                            [weakSelf.contextMenuDelegate
+                                bookmarkURL:item.URL
+                                      title:item.title];
+                          }]];
+          }
         }
+
         if ([weakSelf.contextMenuDelegate
                 respondsToSelector:@selector(closeTabWithIdentifier:
                                                           incognito:)]) {
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
index 7003ac1..eade2c4 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -10,7 +10,9 @@
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #include "ios/chrome/browser/main/browser.h"
@@ -20,6 +22,7 @@
 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/ui/activity_services/activity_params.h"
 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
@@ -76,6 +79,10 @@
   // Use an explicit ivar instead of synthesizing as the setter isn't using the
   // ivar.
   Browser* _incognitoBrowser;
+
+  // The controller that shows the bookmarking UI after the user taps the Add
+  // to Bookmarks button.
+  BookmarkInteractionController* _bookmarkInteractionController;
 }
 
 @property(nonatomic, assign, readonly) Browser* regularBrowser;
@@ -411,6 +418,18 @@
              }];
 }
 
+#pragma mark - Private
+
+// Lazily creates the bookmark interaction controller.
+- (BookmarkInteractionController*)bookmarkInteractionController {
+  if (!_bookmarkInteractionController) {
+    _bookmarkInteractionController = [[BookmarkInteractionController alloc]
+         initWithBrowser:self.regularBrowser
+        parentController:self.baseViewController];
+  }
+  return _bookmarkInteractionController;
+}
+
 #pragma mark - Private (Thumb Strip)
 
 // Whether the thumb strip is enabled.
@@ -858,6 +877,19 @@
   [readingListAdder addToReadingList:command];
 }
 
+- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title {
+  bookmarks::BookmarkModel* bookmarkModel =
+      ios::BookmarkModelFactory::GetForBrowserState(
+          self.regularBrowser->GetBrowserState());
+  bool currentlyBookmarked =
+      bookmarkModel && bookmarkModel->GetMostRecentlyAddedUserNodeForURL(URL);
+
+  [self.bookmarkInteractionController
+      presentBookmarkEditorForURL:URL
+                            title:title
+              currentlyBookmarked:currentlyBookmarked];
+}
+
 - (void)closeTabWithIdentifier:(NSString*)identifier incognito:(BOOL)incognito {
   if (incognito) {
     [self.incognitoTabsMediator closeItemWithID:identifier];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
index e3bd4cb..b5f9afa0 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
@@ -31,6 +31,7 @@
 using chrome_test_util::LongPressCellAndDragToOffsetOf;
 using chrome_test_util::TapAtOffsetOf;
 using chrome_test_util::WindowWithNumber;
+using chrome_test_util::AddToBookmarksButton;
 using chrome_test_util::AddToReadingListButton;
 using chrome_test_util::CloseTabMenuButton;
 
@@ -91,7 +92,9 @@
   if ([self isRunningTest:@selector(testTabGridItemContextMenuShare)] ||
       [self isRunningTest:@selector
             (testTabGridItemContextMenuAddToReadingList)] ||
-      [self isRunningTest:@selector(testTabGridItemContextCloseTab)]) {
+      [self isRunningTest:@selector(testTabGridItemContextCloseTab)] ||
+      [self
+          isRunningTest:@selector(testTabGridItemContextMenuAddToBookmarks)]) {
     config.features_enabled.push_back(kTabGridContextMenu);
   }
   return config;
@@ -327,41 +330,29 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
       performAction:grey_tap()];
 
-  NSString* snackBarLabel =
-      l10n_util::GetNSStringWithFixup(IDS_IOS_READING_LIST_SNACKBAR_MESSAGE);
-  // Start custom monitor, because there's a chance the snackbar is
-  // already gone by the time we wait for it (and it was like that sometimes).
-  [ChromeEarlGrey watchForButtonsWithLabels:@[ snackBarLabel ]
-                                    timeout:kSnackbarAppearanceTimeout];
+  [self longPressTabWithTitle:[NSString stringWithUTF8String:kTitle1]];
+
+  [self waitForSnackBarMessage:IDS_IOS_READING_LIST_SNACKBAR_MESSAGE
+      triggeredByTappingItemWithMatcher:AddToReadingListButton()];
+}
+
+// Tests the Add to Bookmarks action on a tab grid item's context menu.
+- (void)testTabGridItemContextMenuAddToBookmarks {
+  if (!base::ios::IsRunningOnIOS13OrLater()) {
+    EARL_GREY_TEST_SKIPPED(
+        @"Tab Grid context menu only supported on iOS 13 and later.");
+  }
+
+  [ChromeEarlGrey loadURL:_URL1];
+  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
+
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_tap()];
 
   [self longPressTabWithTitle:[NSString stringWithUTF8String:kTitle1]];
 
-  // Add the page to the reading list.
-  [[EarlGrey selectElementWithMatcher:AddToReadingListButton()]
-      performAction:grey_tap()];
-
-  // Wait for the snackbar to appear.
-  id<GREYMatcher> snackbar_matcher =
-      chrome_test_util::ButtonWithAccessibilityLabelId(
-          IDS_IOS_READING_LIST_SNACKBAR_MESSAGE);
-  ConditionBlock wait_for_appearance = ^{
-    return [ChromeEarlGrey watcherDetectedButtonWithLabel:snackBarLabel];
-  };
-  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
-                 kSnackbarAppearanceTimeout, wait_for_appearance),
-             @"Snackbar did not appear.");
-
-  // Wait for the snackbar to disappear.
-  ConditionBlock wait_for_disappearance = ^{
-    NSError* error = nil;
-    [[EarlGrey selectElementWithMatcher:snackbar_matcher]
-        assertWithMatcher:grey_nil()
-                    error:&error];
-    return error == nil;
-  };
-  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
-                 kSnackbarDisappearanceTimeout, wait_for_disappearance),
-             @"Snackbar did not disappear.");
+  [self waitForSnackBarMessage:IDS_IOS_BOOKMARK_PAGE_SAVED
+      triggeredByTappingItemWithMatcher:AddToBookmarksButton()];
 }
 
 // Tests the Share action on a tab grid item's context menu.
@@ -954,4 +945,38 @@
       performAction:grey_tap()];
 }
 
+- (void)waitForSnackBarMessage:(int)messageIdentifier
+    triggeredByTappingItemWithMatcher:(id<GREYMatcher>)matcher {
+  NSString* snackBarLabel = l10n_util::GetNSStringWithFixup(messageIdentifier);
+  // Start custom monitor, because there's a chance the snackbar is
+  // already gone by the time we wait for it (and it was like that sometimes).
+  [ChromeEarlGrey watchForButtonsWithLabels:@[ snackBarLabel ]
+                                    timeout:kSnackbarAppearanceTimeout];
+
+  // Add the page to the reading list.
+  [[EarlGrey selectElementWithMatcher:matcher] performAction:grey_tap()];
+
+  // Wait for the snackbar to appear.
+  id<GREYMatcher> snackbar_matcher =
+      chrome_test_util::ButtonWithAccessibilityLabelId(messageIdentifier);
+  ConditionBlock wait_for_appearance = ^{
+    return [ChromeEarlGrey watcherDetectedButtonWithLabel:snackBarLabel];
+  };
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 kSnackbarAppearanceTimeout, wait_for_appearance),
+             @"Snackbar did not appear.");
+
+  // Wait for the snackbar to disappear.
+  ConditionBlock wait_for_disappearance = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:snackbar_matcher]
+        assertWithMatcher:grey_nil()
+                    error:&error];
+    return error == nil;
+  };
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 kSnackbarDisappearanceTimeout, wait_for_disappearance),
+             @"Snackbar did not disappear.");
+}
+
 @end
diff --git a/ios/chrome/browser/ui/toolbar/fullscreen/BUILD.gn b/ios/chrome/browser/ui/toolbar/fullscreen/BUILD.gn
index efec08ac..508832cb 100644
--- a/ios/chrome/browser/ui/toolbar/fullscreen/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/fullscreen/BUILD.gn
@@ -4,8 +4,6 @@
 
 source_set("fullscreen") {
   sources = [
-    "legacy_toolbar_ui_updater.h",
-    "legacy_toolbar_ui_updater.mm",
     "toolbar_ui.h",
     "toolbar_ui.mm",
   ]
@@ -34,10 +32,7 @@
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
-  sources = [
-    "legacy_toolbar_ui_updater_unittest.mm",
-    "toolbar_ui_broadcasting_util_unittest.mm",
-  ]
+  sources = [ "toolbar_ui_broadcasting_util_unittest.mm" ]
   deps = [
     ":fullscreen",
     ":fullscreen_broadcasting_util",
diff --git a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h b/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h
deleted file mode 100644
index a52b016..0000000
--- a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_FULLSCREEN_LEGACY_TOOLBAR_UI_UPDATER_H_
-#define IOS_CHROME_BROWSER_UI_TOOLBAR_FULLSCREEN_LEGACY_TOOLBAR_UI_UPDATER_H_
-
-#import <UIKit/UIKit.h>
-
-@class ToolbarUIState;
-class WebStateList;
-
-@protocol ToolbarHeightProviderForFullscreen
-// The minimum and maximum amount by which the top toolbar overlaps the browser
-// content area.
-- (CGFloat)collapsedTopToolbarHeight;
-- (CGFloat)expandedTopToolbarHeight;
-// Height of the bottom toolbar.
-- (CGFloat)bottomToolbarHeight;
-@end
-
-// Helper object that uses navigation events to update a ToolbarUIState.
-@interface LegacyToolbarUIUpdater : NSObject
-
-// The toolbar UI being updated by this object.
-@property(nonatomic, strong, readonly, nonnull) ToolbarUIState* toolbarUI;
-
-// Designated initializer that uses navigation events from |webStateList| and
-// the height provided by |owner| to update |state|'s broadcast value.
-- (nullable instancetype)
-initWithToolbarUI:(nonnull ToolbarUIState*)toolbarUI
-     toolbarOwner:(nonnull id<ToolbarHeightProviderForFullscreen>)owner
-     webStateList:(nonnull WebStateList*)webStateList NS_DESIGNATED_INITIALIZER;
-- (nullable instancetype)init NS_UNAVAILABLE;
-
-// Starts updating |state|.
-- (void)startUpdating;
-
-// Stops updating |state|.
-- (void)stopUpdating;
-
-// Forces an update of |state|.
-- (void)updateState;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TOOLBAR_FULLSCREEN_LEGACY_TOOLBAR_UI_UPDATER_H_
diff --git a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.mm b/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.mm
deleted file mode 100644
index 984c390..0000000
--- a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.mm
+++ /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 "ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h"
-
-#include <memory>
-
-#include "base/check_op.h"
-#import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui.h"
-#import "ios/chrome/browser/web_state_list/web_state_list.h"
-#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
-#import "ios/web/public/navigation/navigation_context.h"
-#import "ios/web/public/web_state.h"
-#import "ios/web/public/web_state_observer_bridge.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LegacyToolbarUIUpdater ()<CRWWebStateObserver,
-                                     WebStateListObserving> {
-  // The bridge for WebStateList observation.
-  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
-  // The bridge for WebState observation.
-  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
-}
-// The owner passed on initialization.
-@property(nonatomic, readonly, strong) id<ToolbarHeightProviderForFullscreen>
-    owner;
-// The WebStateList whose navigations are driving this updater.
-@property(nonatomic, readonly) WebStateList* webStateList;
-// The active WebState in |webStateList|.
-@property(nonatomic, assign) web::WebState* webState;
-
-// Updates |state| using |owner|.
-- (void)updateState;
-
-@end
-
-@implementation LegacyToolbarUIUpdater
-@synthesize toolbarUI = _toolbarUI;
-@synthesize owner = _owner;
-@synthesize webStateList = _webStateList;
-@synthesize webState = _webState;
-
-- (nullable instancetype)
-initWithToolbarUI:(nonnull ToolbarUIState*)toolbarUI
-     toolbarOwner:(nonnull id<ToolbarHeightProviderForFullscreen>)owner
-     webStateList:(nonnull WebStateList*)webStateList {
-  if (self = [super init]) {
-    _toolbarUI = toolbarUI;
-    DCHECK(_toolbarUI);
-    _owner = owner;
-    DCHECK(_owner);
-    _webStateList = webStateList;
-    DCHECK(_webStateList);
-  }
-  return self;
-}
-
-#pragma mark Accessors
-
-- (void)setWebState:(web::WebState*)webState {
-  if (_webState == webState)
-    return;
-  if (_webState) {
-    DCHECK(_webStateObserver);
-    _webState->RemoveObserver(_webStateObserver.get());
-    _webStateObserver = nullptr;
-  }
-  _webState = webState;
-  if (_webState) {
-    _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
-    _webState->AddObserver(_webStateObserver.get());
-    [self updateState];
-  }
-}
-
-#pragma mark Public
-
-- (void)startUpdating {
-  DCHECK(!_webStateListObserver);
-  DCHECK(!_webStateObserver);
-  _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
-  self.webStateList->AddObserver(_webStateListObserver.get());
-  self.webState = self.webStateList->GetActiveWebState();
-  [self updateState];
-}
-
-- (void)stopUpdating {
-  DCHECK(_webStateListObserver);
-  DCHECK(!self.webState || _webStateObserver);
-  self.webStateList->RemoveObserver(_webStateListObserver.get());
-  self.webState = nullptr;
-}
-
-- (void)updateState {
-  self.toolbarUI.collapsedHeight = [self.owner collapsedTopToolbarHeight];
-  self.toolbarUI.expandedHeight = [self.owner expandedTopToolbarHeight];
-  self.toolbarUI.bottomToolbarHeight = [self.owner bottomToolbarHeight];
-}
-
-#pragma mark CRWWebStateObserver
-
-- (void)webState:(web::WebState*)webState
-    didStartNavigation:(web::NavigationContext*)navigation {
-  // For user-initiated loads, the toolbar is updated when the navigation is
-  // started.
-  if (!navigation->IsRendererInitiated())
-    [self updateState];
-}
-
-- (void)webState:(web::WebState*)webState
-    didFinishNavigation:(web::NavigationContext*)navigation {
-  [self updateState];
-}
-
-- (void)webStateDestroyed:(web::WebState*)webState {
-  self.webState = nullptr;
-}
-
-#pragma mark WebStateListObserving
-
-- (void)webStateList:(WebStateList*)webStateList
-    didReplaceWebState:(web::WebState*)oldWebState
-          withWebState:(web::WebState*)newWebState
-               atIndex:(int)atIndex {
-  DCHECK_EQ(self.webStateList, webStateList);
-  if (newWebState == webStateList->GetActiveWebState())
-    self.webState = newWebState;
-}
-
-- (void)webStateList:(WebStateList*)webStateList
-    didChangeActiveWebState:(web::WebState*)newWebState
-                oldWebState:(web::WebState*)oldWebState
-                    atIndex:(int)atIndex
-                     reason:(ActiveWebStateChangeReason)reason {
-  DCHECK_EQ(self.webStateList, webStateList);
-  self.webState = newWebState;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater_unittest.mm b/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater_unittest.mm
deleted file mode 100644
index 87dd6ac..0000000
--- a/ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater_unittest.mm
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/toolbar/fullscreen/legacy_toolbar_ui_updater.h"
-
-#include <memory>
-
-#import "ios/chrome/browser/ui/toolbar/fullscreen/toolbar_ui.h"
-#include "ios/chrome/browser/ui/util/ui_util.h"
-#import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
-#import "ios/chrome/browser/web_state_list/web_state_list.h"
-#import "ios/chrome/browser/web_state_list/web_state_opener.h"
-#import "ios/web/public/test/fakes/fake_navigation_context.h"
-#import "ios/web/public/test/fakes/fake_web_state.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface TestToolbarOwner : NSObject<ToolbarHeightProviderForFullscreen>
-// Define writable property with same name as |-collapsedTopToolbarHeight| and
-// |-expandedTopToolbarHeight| getters defined in
-// ToolbarHeightProviderForFullscreen.
-@property(nonatomic, assign) CGFloat collapsedTopToolbarHeight;
-@property(nonatomic, assign) CGFloat expandedTopToolbarHeight;
-@property(nonatomic, assign) CGFloat bottomToolbarHeight;
-@end
-
-@implementation TestToolbarOwner
-@synthesize collapsedTopToolbarHeight = _collapsedTopToolbarHeight;
-@synthesize expandedTopToolbarHeight = _expandedTopToolbarHeight;
-@synthesize bottomToolbarHeight = _bottomToolbarHeight;
-@end
-
-class LegacyToolbarUIUpdaterTest : public PlatformTest {
- public:
-  LegacyToolbarUIUpdaterTest()
-      : PlatformTest(),
-        web_state_list_(&web_state_list_delegate_),
-        toolbar_owner_([[TestToolbarOwner alloc] init]),
-        toolbar_ui_([[ToolbarUIState alloc] init]),
-        updater_([[LegacyToolbarUIUpdater alloc]
-            initWithToolbarUI:toolbar_ui_
-                 toolbarOwner:toolbar_owner_
-                 webStateList:&web_state_list_]) {}
-  ~LegacyToolbarUIUpdaterTest() override { StopUpdating(); }
-
-  // Getters.
-  WebStateList* web_state_list() { return &web_state_list_; }
-  TestToolbarOwner* toolbar_owner() { return toolbar_owner_; }
-  CGFloat collapsed_toolbar_height() { return toolbar_ui_.collapsedHeight; }
-  CGFloat expanded_toolbar_height() { return toolbar_ui_.expandedHeight; }
-  CGFloat bottom_toolbar_height() { return toolbar_ui_.bottomToolbarHeight; }
-
-  // Start or stop updating the state.
-  void StartUpdating() {
-    if (updating_)
-      return;
-    [updater_ startUpdating];
-    updating_ = true;
-  }
-  void StopUpdating() {
-    if (!updating_)
-      return;
-    [updater_ stopUpdating];
-    updating_ = false;
-  }
-
-  // Inserts and activates a new WebState at the end of the list, and returns a
-  // pointer to the inserted WebState.
-  web::FakeWebState* InsertActiveWebState() {
-    auto web_state = std::make_unique<web::FakeWebState>();
-    web::FakeWebState* inserted_web_state = web_state.get();
-    web_state_list_.InsertWebState(0, std::move(web_state),
-                                   WebStateList::INSERT_ACTIVATE,
-                                   WebStateOpener(nullptr));
-    return inserted_web_state;
-  }
-
- private:
-  FakeWebStateListDelegate web_state_list_delegate_;
-  WebStateList web_state_list_;
-  __strong TestToolbarOwner* toolbar_owner_ = nil;
-  __strong ToolbarUIState* toolbar_ui_ = nil;
-  __strong LegacyToolbarUIUpdater* updater_ = nil;
-  bool updating_ = false;
-};
-
-// Tests that |-startUpdating| resets the state's height when starting.
-TEST_F(LegacyToolbarUIUpdaterTest, StartUpdating) {
-  EXPECT_EQ(expanded_toolbar_height(), 0.0);
-  const CGFloat kCollapsedHeight = 140.0;
-  const CGFloat kExpandedHeight = 150.0;
-  const CGFloat kBottomHeight = 160.0;
-  toolbar_owner().collapsedTopToolbarHeight = kCollapsedHeight;
-  toolbar_owner().expandedTopToolbarHeight = kExpandedHeight;
-  toolbar_owner().bottomToolbarHeight = kBottomHeight;
-  StartUpdating();
-  EXPECT_EQ(collapsed_toolbar_height(), kCollapsedHeight);
-  EXPECT_EQ(expanded_toolbar_height(), kExpandedHeight);
-  EXPECT_EQ(bottom_toolbar_height(), kBottomHeight);
-}
-
-// Tests that the state is not updated after calling |-stopUpdating|.
-TEST_F(LegacyToolbarUIUpdaterTest, StopUpdating) {
-  web::FakeWebState* web_state = InsertActiveWebState();
-  StartUpdating();
-  const CGFloat kHeight = 150.0;
-  toolbar_owner().expandedTopToolbarHeight = kHeight;
-  web::FakeNavigationContext context;
-  web_state->OnNavigationFinished(&context);
-  EXPECT_EQ(expanded_toolbar_height(), kHeight);
-  const CGFloat kNonUpdatedHeight = 500.0;
-  StopUpdating();
-  toolbar_owner().expandedTopToolbarHeight = kNonUpdatedHeight;
-  web_state->OnNavigationFinished(&context);
-  EXPECT_EQ(expanded_toolbar_height(), kHeight);
-}
-
-// Tests that the updater polls for the new height when the active WebState
-// changes.
-TEST_F(LegacyToolbarUIUpdaterTest, UpdateActiveWebState) {
-  StartUpdating();
-  const CGFloat kCollapsedHeight = 140.0;
-  const CGFloat kExpandedHeight = 150.0;
-  const CGFloat kBottomHeight = 160.0;
-  toolbar_owner().collapsedTopToolbarHeight = kCollapsedHeight;
-  toolbar_owner().expandedTopToolbarHeight = kExpandedHeight;
-  toolbar_owner().bottomToolbarHeight = kBottomHeight;
-  EXPECT_EQ(collapsed_toolbar_height(), 0.0);
-  EXPECT_EQ(expanded_toolbar_height(), 0.0);
-  EXPECT_EQ(bottom_toolbar_height(), 0.0);
-  InsertActiveWebState();
-  EXPECT_EQ(collapsed_toolbar_height(), kCollapsedHeight);
-  EXPECT_EQ(expanded_toolbar_height(), kExpandedHeight);
-  EXPECT_EQ(bottom_toolbar_height(), kBottomHeight);
-}
-
-// Tests that the updater polls for the new height when the active WebState
-// starts a user-initiated navigation.
-TEST_F(LegacyToolbarUIUpdaterTest, UserInitiatedNavigation) {
-  web::FakeWebState* web_state = InsertActiveWebState();
-  StartUpdating();
-  const CGFloat kCollapsedHeight = 140.0;
-  const CGFloat kExpandedHeight = 150.0;
-  const CGFloat kBottomHeight = 160.0;
-  toolbar_owner().collapsedTopToolbarHeight = kCollapsedHeight;
-  toolbar_owner().expandedTopToolbarHeight = kExpandedHeight;
-  toolbar_owner().bottomToolbarHeight = kBottomHeight;
-  EXPECT_EQ(collapsed_toolbar_height(), 0.0);
-  EXPECT_EQ(expanded_toolbar_height(), 0.0);
-  EXPECT_EQ(bottom_toolbar_height(), 0.0);
-  web::FakeNavigationContext context;
-  context.SetIsRendererInitiated(false);
-  web_state->OnNavigationStarted(&context);
-  EXPECT_EQ(collapsed_toolbar_height(), kCollapsedHeight);
-  EXPECT_EQ(expanded_toolbar_height(), kExpandedHeight);
-  EXPECT_EQ(bottom_toolbar_height(), kBottomHeight);
-}
-
-// Tests that the updater waits until a render-initiated navigation is committed
-// before updating the ui state.
-TEST_F(LegacyToolbarUIUpdaterTest, RendererInitiatedNavigation) {
-  web::FakeWebState* web_state = InsertActiveWebState();
-  StartUpdating();
-  const CGFloat kCollapsedHeight = 140.0;
-  const CGFloat kExpandedHeight = 150.0;
-  const CGFloat kBottomHeight = 160.0;
-  toolbar_owner().collapsedTopToolbarHeight = kCollapsedHeight;
-  toolbar_owner().expandedTopToolbarHeight = kExpandedHeight;
-  toolbar_owner().bottomToolbarHeight = kBottomHeight;
-  EXPECT_EQ(collapsed_toolbar_height(), 0.0);
-  EXPECT_EQ(expanded_toolbar_height(), 0.0);
-  EXPECT_EQ(bottom_toolbar_height(), 0.0);
-  web::FakeNavigationContext context;
-  context.SetIsRendererInitiated(true);
-  web_state->OnNavigationStarted(&context);
-  EXPECT_EQ(collapsed_toolbar_height(), 0.0);
-  EXPECT_EQ(expanded_toolbar_height(), 0.0);
-  EXPECT_EQ(bottom_toolbar_height(), 0.0);
-  web_state->OnNavigationFinished(&context);
-  EXPECT_EQ(collapsed_toolbar_height(), kCollapsedHeight);
-  EXPECT_EQ(expanded_toolbar_height(), kExpandedHeight);
-  EXPECT_EQ(bottom_toolbar_height(), kBottomHeight);
-}
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index 709dbaf..a88751e 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -139,6 +139,9 @@
 // Matcher for Add to reading list button.
 id<GREYMatcher> AddToReadingListButton();
 
+// Matcher for Add to bookmarks button.
+id<GREYMatcher> AddToBookmarksButton();
+
 // Matcher for SettingsSwitchCell.
 id<GREYMatcher> SettingsSwitchCell(NSString* accessibility_identifier,
                                    BOOL is_toggled_on);
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index 4af257e..ab40024 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -178,6 +178,10 @@
   return [ChromeMatchersAppInterface addToReadingListButton];
 }
 
+id<GREYMatcher> AddToBookmarksButton() {
+  return [ChromeMatchersAppInterface addToBookmarksButton];
+}
+
 id<GREYMatcher> CloseTabMenuButton() {
   return [ChromeMatchersAppInterface closeTabMenuButton];
 }
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
index a0452d0..6b5bed16 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
@@ -133,6 +133,9 @@
 // Matcher for Add to reading list button.
 + (id<GREYMatcher>)addToReadingListButton;
 
+// Matcher for Add to bookmarks button.
++ (id<GREYMatcher>)addToBookmarksButton;
+
 // Matcher for SettingsSwitchCell.
 + (id<GREYMatcher>)settingsSwitchCell:(NSString*)accessibilityIdentifier
                           isToggledOn:(BOOL)isToggledOn;
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 6efcf73..7aa913b 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -389,6 +389,13 @@
                     grey_sufficientlyVisible(), nil);
 }
 
++ (id<GREYMatcher>)addToBookmarksButton {
+  return grey_allOf(
+      [ChromeMatchersAppInterface buttonWithAccessibilityLabelID:
+                                      (IDS_IOS_CONTENT_CONTEXT_ADDTOBOOKMARKS)],
+      grey_sufficientlyVisible(), nil);
+}
+
 + (id<GREYMatcher>)settingsSwitchCell:(NSString*)accessibilityIdentifier
                           isToggledOn:(BOOL)isToggledOn {
   return [ChromeMatchersAppInterface settingsSwitchCell:accessibilityIdentifier
diff --git a/ios/web/js_messaging/crw_js_window_id_manager.mm b/ios/web/js_messaging/crw_js_window_id_manager.mm
index c78ef2cd..ee6b8b6 100644
--- a/ios/web/js_messaging/crw_js_window_id_manager.mm
+++ b/ios/web/js_messaging/crw_js_window_id_manager.mm
@@ -4,6 +4,7 @@
 
 #import "ios/web/js_messaging/crw_js_window_id_manager.h"
 
+#include "base/dcheck_is_on.h"
 #include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
@@ -18,6 +19,17 @@
 // Number of random bytes in unique key for window ID. The length of the
 // window ID will be twice this number, as it is hexadecimal encoded.
 const size_t kUniqueKeyLength = 16;
+
+#if DCHECK_IS_ON()
+// Returns whether |error| represents a failure to execute JavaScript due to
+// JavaScript execution being disallowed.
+bool IsJavaScriptExecutionProhibitedError(NSError* error) {
+  return error.code == WKErrorJavaScriptExceptionOccurred &&
+         [@"Cannot execute JavaScript in this document"
+             isEqualToString:error.userInfo[@"WKJavaScriptExceptionMessage"]];
+}
+#endif
+
 }  // namespace
 
 @interface CRWJSWindowIDManager () {
@@ -61,10 +73,13 @@
   [_webView evaluateJavaScript:scriptWithResult
              completionHandler:^(id result, NSError* error) {
                if (error) {
+#if DCHECK_IS_ON()
                  DCHECK(error.code == WKErrorWebViewInvalidated ||
-                        error.code == WKErrorWebContentProcessTerminated)
+                        error.code == WKErrorWebContentProcessTerminated ||
+                        IsJavaScriptExecutionProhibitedError(error))
                      << scriptWithResult << " failed with error "
                      << base::SysNSStringToUTF8(error.description);
+#endif
                  return;
                }
 
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
index 912b1dc..d91b9931 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
@@ -16,8 +16,8 @@
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
 #include "components/autofill/core/browser/payments/legal_message_line.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync/driver/sync_service.h"
 #import "ios/web/public/web_state.h"
diff --git a/ios/web_view/internal/autofill/web_view_strike_database_factory.mm b/ios/web_view/internal/autofill/web_view_strike_database_factory.mm
index a8611ab..3e07c1b 100644
--- a/ios/web_view/internal/autofill/web_view_strike_database_factory.mm
+++ b/ios/web_view/internal/autofill/web_view_strike_database_factory.mm
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "base/no_destructor.h"
-#include "components/autofill/core/browser/payments/strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "ios/web_view/internal/app/application_context.h"
 #include "ios/web_view/internal/web_view_browser_state.h"
diff --git a/media/audio/audio_output_stream_sink.h b/media/audio/audio_output_stream_sink.h
index 1c3eefe6..fcc8bc1 100644
--- a/media/audio/audio_output_stream_sink.h
+++ b/media/audio/audio_output_stream_sink.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
diff --git a/media/audio/audio_source_parameters.h b/media/audio/audio_source_parameters.h
index 96e1c19..09f4d27 100644
--- a/media/audio/audio_source_parameters.h
+++ b/media/audio/audio_source_parameters.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_AUDIO_AUDIO_SOURCE_PARAMETERS_H_
 #define MEDIA_AUDIO_AUDIO_SOURCE_PARAMETERS_H_
 
-#include <string>
-
 #include "base/optional.h"
 #include "base/unguessable_token.h"
 #include "media/base/audio_processing.h"
diff --git a/media/audio/audio_thread_impl.h b/media/audio/audio_thread_impl.h
index 77b0fcd..7010035 100644
--- a/media/audio/audio_thread_impl.h
+++ b/media/audio/audio_thread_impl.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_AUDIO_AUDIO_THREAD_IMPL_H_
 #define MEDIA_AUDIO_AUDIO_THREAD_IMPL_H_
 
-#include <memory>
-
 #include "base/sequenced_task_runner.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_checker.h"
diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc
index 5183348..cc59e9fa 100644
--- a/media/audio/win/audio_low_latency_input_win.cc
+++ b/media/audio/win/audio_low_latency_input_win.cc
@@ -619,11 +619,10 @@
 
   const bool monotonic_timestamps =
       min_timestamp_diff_ >= base::TimeDelta::FromMicroseconds(1);
-  if (!monotonic_timestamps) {
-    SendLogMessage("%s => (ERROR: non-monotonic timestamp sequence)", __func__);
-  }
   base::UmaHistogramBoolean("Media.Audio.Capture.Win.MonotonicTimestamps",
                             monotonic_timestamps);
+  SendLogMessage("%s => (Media.Audio.Capture.Win.MonotonicTimestamps=%s)",
+                 __func__, monotonic_timestamps ? "true" : "false");
 
   started_ = false;
   sink_ = nullptr;
@@ -803,6 +802,8 @@
 
   record_start_time_ = base::TimeTicks::Now();
   last_capture_time_ = base::TimeTicks();
+  max_timestamp_diff_ = base::TimeDelta::Min();
+  min_timestamp_diff_ = base::TimeDelta::Max();
 
   while (recording && !error) {
     // Wait for a close-down event or a new capture event.
@@ -852,7 +853,7 @@
       audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet);
   if (FAILED(hr)) {
     LOG(ERROR) << "WAIS::" << __func__
-               << " => (ERROR: IAudioCaptureClient::GetNextPacketSize=["
+               << " => (ERROR: 1-IAudioCaptureClient::GetNextPacketSize=["
                << ErrorToString(hr).c_str() << "])";
     return;
   }
@@ -876,6 +877,11 @@
       DCHECK_EQ(num_frames_to_read, 0u);
       return;
     }
+    if (hr == AUDCLNT_E_OUT_OF_ORDER) {
+      // A previous IAudioCaptureClient::GetBuffer() call is still in effect.
+      // Release any acquired buffer to be able to try reading a buffer again.
+      audio_capture_client_->ReleaseBuffer(num_frames_to_read);
+    }
     if (FAILED(hr)) {
       LOG(ERROR) << "WAIS::" << __func__
                  << " => (ERROR: IAudioCaptureClient::GetBuffer=["
@@ -941,11 +947,9 @@
     }
 
     // Keep track of max and min time difference between two successive time-
-    // stamps. Currently only used for logging purposes.
-    if (last_capture_time_.is_null()) {
-      max_timestamp_diff_ = base::TimeDelta::Min();
-      min_timestamp_diff_ = base::TimeDelta::Max();
-    } else {
+    // stamps. Results are used in Stop() to verify that the time-stamp sequence
+    // was monotonic.
+    if (!last_capture_time_.is_null()) {
       const auto delta_ts = capture_time - last_capture_time_;
       DCHECK_GT(device_position, 0u);
       DCHECK_GT(delta_ts, base::TimeDelta::Min());
@@ -1017,7 +1021,7 @@
     hr = audio_capture_client_->GetNextPacketSize(&num_frames_in_next_packet);
     if (FAILED(hr)) {
       LOG(ERROR) << "WAIS::" << __func__
-                 << " => (ERROR: IAudioCaptureClient::GetNextPacketSize=["
+                 << " => (ERROR: 2-IAudioCaptureClient::GetNextPacketSize=["
                  << ErrorToString(hr).c_str() << "])";
       return;
     }
diff --git a/media/base/android/media_crypto_context_impl.h b/media/base/android/media_crypto_context_impl.h
index 435c96d9..27e9eda7 100644
--- a/media/base/android/media_crypto_context_impl.h
+++ b/media/base/android/media_crypto_context_impl.h
@@ -7,8 +7,6 @@
 
 #include <jni.h>
 
-#include <memory>
-
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
 #include "media/base/android/media_crypto_context.h"
diff --git a/media/base/android/mock_media_crypto_context.h b/media/base/android/mock_media_crypto_context.h
index a1136135..1cd6480 100644
--- a/media/base/android/mock_media_crypto_context.h
+++ b/media/base/android/mock_media_crypto_context.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_BASE_ANDROID_MOCK_MEDIA_CRYPTO_CONTEXT_H_
 #define MEDIA_BASE_ANDROID_MOCK_MEDIA_CRYPTO_CONTEXT_H_
 
-#include <memory>
-
 #include "base/macros.h"
 #include "media/base/android/media_crypto_context.h"
 #include "media/base/cdm_context.h"
diff --git a/media/base/audio_decoder.h b/media/base/audio_decoder.h
index 245ac29..961d570e 100644
--- a/media/base/audio_decoder.h
+++ b/media/base/audio_decoder.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_BASE_AUDIO_DECODER_H_
 #define MEDIA_BASE_AUDIO_DECODER_H_
 
-#include <string>
-
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
diff --git a/media/base/fake_audio_renderer_sink.h b/media/base/fake_audio_renderer_sink.h
index 9918eab..63af4b5 100644
--- a/media/base/fake_audio_renderer_sink.h
+++ b/media/base/fake_audio_renderer_sink.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "base/macros.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/audio_renderer_sink.h"
diff --git a/media/base/memory_dump_provider_proxy.h b/media/base/memory_dump_provider_proxy.h
index 0473c794..0bdda75b7 100644
--- a/media/base/memory_dump_provider_proxy.h
+++ b/media/base/memory_dump_provider_proxy.h
@@ -8,10 +8,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <memory>
-#include <string>
-#include <utility>
-
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
diff --git a/media/base/status_codes.h b/media/base/status_codes.h
index 45eee646..70078b0 100644
--- a/media/base/status_codes.h
+++ b/media/base/status_codes.h
@@ -94,6 +94,7 @@
   kGetQuantBufferFailed = 0x00000328,
   kReleaseQuantBufferFailed = 0x00000329,
   kBitstreamBufferSliceTooBig = 0x00000330,
+  kCreateSharedImageFailed = 0x00000331,
 
   // MojoDecoder Errors: 0x04
   kMojoDecoderNoWrappedDecoder = 0x00000401,
diff --git a/media/base/video_decoder.h b/media/base/video_decoder.h
index 8c6cb29..1fa9402 100644
--- a/media/base/video_decoder.h
+++ b/media/base/video_decoder.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_BASE_VIDEO_DECODER_H_
 #define MEDIA_BASE_VIDEO_DECODER_H_
 
-#include <string>
-
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "media/base/decode_status.h"
diff --git a/media/base/video_frame_metadata.h b/media/base/video_frame_metadata.h
index 3456e070..7f77250 100644
--- a/media/base/video_frame_metadata.h
+++ b/media/base/video_frame_metadata.h
@@ -5,9 +5,6 @@
 #ifndef MEDIA_BASE_VIDEO_FRAME_METADATA_H_
 #define MEDIA_BASE_VIDEO_FRAME_METADATA_H_
 
-#include <memory>
-#include <string>
-
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/time/time.h"
diff --git a/media/blink/cache_util.h b/media/blink/cache_util.h
index 16e5c317..f354b9d 100644
--- a/media/blink/cache_util.h
+++ b/media/blink/cache_util.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <vector>
-
 #include "base/time/time.h"
 #include "media/blink/media_blink_export.h"
 
diff --git a/media/blink/multibuffer_reader.h b/media/blink/multibuffer_reader.h
index 137f3888..ae67cca 100644
--- a/media/blink/multibuffer_reader.h
+++ b/media/blink/multibuffer_reader.h
@@ -7,10 +7,6 @@
 
 #include <stdint.h>
 
-#include <limits>
-#include <map>
-#include <set>
-
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "media/blink/media_blink_export.h"
diff --git a/media/capture/content/android/screen_capture_machine_android.h b/media/capture/content/android/screen_capture_machine_android.h
index 4b11834..c1246e5a 100644
--- a/media/capture/content/android/screen_capture_machine_android.h
+++ b/media/capture/content/android/screen_capture_machine_android.h
@@ -6,7 +6,6 @@
 #define MEDIA_CAPTURE_CONTENT_ANDROID_SCREEN_CAPTURE_MACHINE_ANDROID_H_
 
 #include <jni.h>
-#include <memory>
 
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/scoped_refptr.h"
diff --git a/media/cast/cast_environment.h b/media/cast/cast_environment.h
index bff6604e..7af484e 100644
--- a/media/cast/cast_environment.h
+++ b/media/cast/cast_environment.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_CAST_CAST_ENVIRONMENT_H_
 #define MEDIA_CAST_CAST_ENVIRONMENT_H_
 
-#include <memory>
-
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
diff --git a/media/cast/common/mod_util.h b/media/cast/common/mod_util.h
index fc68c63..2761df6 100644
--- a/media/cast/common/mod_util.h
+++ b/media/cast/common/mod_util.h
@@ -5,7 +5,6 @@
 #ifndef MEDIA_CAST_COMMON_MOD_UTIL_H_
 #define MEDIA_CAST_COMMON_MOD_UTIL_H_
 
-#include <map>
 #include "base/check.h"
 
 namespace media {
diff --git a/media/cast/logging/logging_defines.h b/media/cast/logging/logging_defines.h
index 0199c45..c3a668c 100644
--- a/media/cast/logging/logging_defines.h
+++ b/media/cast/logging/logging_defines.h
@@ -8,10 +8,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <map>
-#include <string>
-#include <vector>
-
 #include "base/time/time.h"
 #include "media/cast/common/frame_id.h"
 #include "media/cast/common/rtp_time.h"
diff --git a/media/cast/logging/stats_event_subscriber.h b/media/cast/logging/stats_event_subscriber.h
index 8c88c920..b2e2b410 100644
--- a/media/cast/logging/stats_event_subscriber.h
+++ b/media/cast/logging/stats_event_subscriber.h
@@ -8,7 +8,10 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <map>
 #include <memory>
+#include <utility>
+#include <vector>
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
diff --git a/media/cast/net/rtcp/rtcp_builder.h b/media/cast/net/rtcp/rtcp_builder.h
index 7ff7050..10bd9047 100644
--- a/media/cast/net/rtcp/rtcp_builder.h
+++ b/media/cast/net/rtcp/rtcp_builder.h
@@ -8,9 +8,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <list>
-#include <string>
-
 #include "base/big_endian.h"
 #include "base/macros.h"
 #include "media/cast/net/cast_transport_config.h"
diff --git a/media/cast/net/rtp/rtp_packetizer.h b/media/cast/net/rtp/rtp_packetizer.h
index bb689541..4d1ee9db 100644
--- a/media/cast/net/rtp/rtp_packetizer.h
+++ b/media/cast/net/rtp/rtp_packetizer.h
@@ -9,8 +9,6 @@
 #include <stdint.h>
 
 #include <cmath>
-#include <list>
-#include <map>
 
 #include "base/time/time.h"
 #include "media/cast/common/rtp_time.h"
diff --git a/media/cast/sender/video_frame_factory.h b/media/cast/sender/video_frame_factory.h
index 367f2265..1db6ebec 100644
--- a/media/cast/sender/video_frame_factory.h
+++ b/media/cast/sender/video_frame_factory.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_CAST_SENDER_VIDEO_FRAME_FACTORY_H_
 #define MEDIA_CAST_SENDER_VIDEO_FRAME_FACTORY_H_
 
-#include <memory>
-
 #include "base/time/time.h"
 
 namespace gfx {
diff --git a/media/cast/sender/vp8_encoder.h b/media/cast/sender/vp8_encoder.h
index a6080c4..00afbfdc 100644
--- a/media/cast/sender/vp8_encoder.h
+++ b/media/cast/sender/vp8_encoder.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <memory>
-
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
 #include "media/base/feedback_signal_accumulator.h"
diff --git a/media/cast/test/skewed_single_thread_task_runner.h b/media/cast/test/skewed_single_thread_task_runner.h
index ef949bf8..509b05fe 100644
--- a/media/cast/test/skewed_single_thread_task_runner.h
+++ b/media/cast/test/skewed_single_thread_task_runner.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_CAST_TEST_SKEWED_SINGLE_THREAD_TASK_RUNNER_H_
 #define MEDIA_CAST_TEST_SKEWED_SINGLE_THREAD_TASK_RUNNER_H_
 
-#include <map>
-
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
diff --git a/media/cdm/aes_cbc_crypto.h b/media/cdm/aes_cbc_crypto.h
index b6e0ee59..a01ebc0 100644
--- a/media/cdm/aes_cbc_crypto.h
+++ b/media/cdm/aes_cbc_crypto.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "base/containers/span.h"
 #include "base/macros.h"
 #include "media/base/media_export.h"
diff --git a/media/cdm/cdm_context_ref_impl.h b/media/cdm/cdm_context_ref_impl.h
index 299690c..7ea73f9 100644
--- a/media/cdm/cdm_context_ref_impl.h
+++ b/media/cdm/cdm_context_ref_impl.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_CDM_CDM_CONTEXT_REF_IMPL_H_
 #define MEDIA_CDM_CDM_CONTEXT_REF_IMPL_H_
 
-#include <memory>
-
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
diff --git a/media/cdm/cdm_wrapper.h b/media/cdm/cdm_wrapper.h
index ef0eab6..d911516 100644
--- a/media/cdm/cdm_wrapper.h
+++ b/media/cdm/cdm_wrapper.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "base/check.h"
 #include "base/compiler_specific.h"
 #include "base/feature_list.h"
diff --git a/media/filters/file_data_source.h b/media/filters/file_data_source.h
index 60fdcbe..7b5fd6d 100644
--- a/media/filters/file_data_source.h
+++ b/media/filters/file_data_source.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/memory_mapped_file.h"
diff --git a/media/formats/mp4/dolby_vision.h b/media/formats/mp4/dolby_vision.h
index c1bc9bd0..4ce6c1c 100644
--- a/media/formats/mp4/dolby_vision.h
+++ b/media/formats/mp4/dolby_vision.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_FORMATS_MP4_DOLBY_VISION_H_
 #define MEDIA_FORMATS_MP4_DOLBY_VISION_H_
 
-#include <vector>
-
 #include "base/memory/ref_counted.h"
 #include "media/base/media_export.h"
 #include "media/formats/mp4/box_definitions.h"
diff --git a/media/gpu/android/direct_shared_image_video_provider.h b/media/gpu/android/direct_shared_image_video_provider.h
index 71e59616c..fd6549f 100644
--- a/media/gpu/android/direct_shared_image_video_provider.h
+++ b/media/gpu/android/direct_shared_image_video_provider.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_ANDROID_DIRECT_SHARED_IMAGE_VIDEO_PROVIDER_H_
 #define MEDIA_GPU_ANDROID_DIRECT_SHARED_IMAGE_VIDEO_PROVIDER_H_
 
-#include <memory>
-
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
diff --git a/media/gpu/android/promotion_hint_aggregator_impl.h b/media/gpu/android/promotion_hint_aggregator_impl.h
index 9130800..1d33c4c 100644
--- a/media/gpu/android/promotion_hint_aggregator_impl.h
+++ b/media/gpu/android/promotion_hint_aggregator_impl.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_ANDROID_PROMOTION_HINT_AGGREGATOR_IMPL_H_
 #define MEDIA_GPU_ANDROID_PROMOTION_HINT_AGGREGATOR_IMPL_H_
 
-#include <memory>
-
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
diff --git a/media/gpu/av1_picture.h b/media/gpu/av1_picture.h
index 505c9cd..d770f9f 100644
--- a/media/gpu/av1_picture.h
+++ b/media/gpu/av1_picture.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_AV1_PICTURE_H_
 #define MEDIA_GPU_AV1_PICTURE_H_
 
-#include <memory>
-
 #include "media/gpu/codec_picture.h"
 #include "media/gpu/media_gpu_export.h"
 #include "third_party/libgav1/src/src/utils/types.h"
diff --git a/media/gpu/chromeos/vda_video_frame_pool.h b/media/gpu/chromeos/vda_video_frame_pool.h
index fd2995a..a7ec94a 100644
--- a/media/gpu/chromeos/vda_video_frame_pool.h
+++ b/media/gpu/chromeos/vda_video_frame_pool.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_CHROMEOS_VDA_VIDEO_FRAME_POOL_H_
 #define MEDIA_GPU_CHROMEOS_VDA_VIDEO_FRAME_POOL_H_
 
-#include <vector>
-
 #include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
diff --git a/media/gpu/command_buffer_helper.cc b/media/gpu/command_buffer_helper.cc
index a15549c0..36c9aab5 100644
--- a/media/gpu/command_buffer_helper.cc
+++ b/media/gpu/command_buffer_helper.cc
@@ -32,7 +32,10 @@
       public gpu::CommandBufferStub::DestructionObserver {
  public:
   explicit CommandBufferHelperImpl(gpu::CommandBufferStub* stub)
-      : CommandBufferHelper(stub->channel()->task_runner()), stub_(stub) {
+      : CommandBufferHelper(stub->channel()->task_runner()),
+        stub_(stub),
+        memory_tracker_(this),
+        memory_type_tracker_(&memory_tracker_) {
     DVLOG(1) << __func__;
     DCHECK(stub_->channel()->task_runner()->BelongsToCurrentThread());
 
@@ -48,8 +51,6 @@
 #endif  // defined(OS_MAC)
     );
     decoder_helper_ = GLES2DecoderHelper::Create(stub_->decoder_context());
-    tracker_ =
-        std::make_unique<gpu::MemoryTypeTracker>(stub_->GetMemoryTracker());
   }
 
   gl::GLContext* GetGLContext() override {
@@ -63,6 +64,11 @@
   }
 
   gpu::SharedImageStub* GetSharedImageStub() override {
+    return shared_image_stub();
+  }
+
+  // Const variant of above method for internal callers.
+  gpu::SharedImageStub* shared_image_stub() const {
     if (!stub_)
       return nullptr;
     return stub_->channel()->shared_image_stub();
@@ -89,7 +95,7 @@
     return stub_->channel()
         ->gpu_channel_manager()
         ->shared_image_manager()
-        ->Register(std::move(backing), tracker_.get());
+        ->Register(std::move(backing), &memory_type_tracker_);
   }
 
   gpu::TextureBase* GetTexture(GLuint service_id) const override {
@@ -209,6 +215,51 @@
   }
 
  private:
+  // Helper class to forward memory tracking calls to shared image stub.
+  // Necessary because the underlying stub and channel can get destroyed before
+  // the CommandBufferHelper and its clients.
+  class MemoryTrackerImpl : public gpu::MemoryTracker {
+   public:
+    explicit MemoryTrackerImpl(CommandBufferHelperImpl* helper)
+        : helper_(helper) {
+      if (auto* stub = helper_->shared_image_stub()) {
+        // We assume these don't change after initialization.
+        client_id_ = stub->ClientId();
+        client_tracing_id_ = stub->ClientTracingId();
+        context_group_tracing_id_ = stub->ContextGroupTracingId();
+      }
+    }
+    ~MemoryTrackerImpl() override = default;
+
+    MemoryTrackerImpl(const MemoryTrackerImpl&) = delete;
+    MemoryTrackerImpl& operator=(const MemoryTrackerImpl&) = delete;
+
+    void TrackMemoryAllocatedChange(int64_t delta) override {
+      if (auto* stub = helper_->shared_image_stub())
+        stub->TrackMemoryAllocatedChange(delta);
+    }
+
+    uint64_t GetSize() const override {
+      if (auto* stub = helper_->shared_image_stub())
+        return stub->GetSize();
+      return 0;
+    }
+
+    int ClientId() const override { return client_id_; }
+
+    uint64_t ClientTracingId() const override { return client_tracing_id_; }
+
+    uint64_t ContextGroupTracingId() const override {
+      return context_group_tracing_id_;
+    }
+
+   private:
+    CommandBufferHelperImpl* const helper_;
+    int client_id_ = 0;
+    uint64_t client_tracing_id_ = 0;
+    uint64_t context_group_tracing_id_ = 0;
+  };
+
   ~CommandBufferHelperImpl() override {
     DVLOG(1) << __func__;
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -257,7 +308,8 @@
 
   WillDestroyStubCB will_destroy_stub_cb_;
 
-  std::unique_ptr<gpu::MemoryTypeTracker> tracker_;
+  MemoryTrackerImpl memory_tracker_;
+  gpu::MemoryTypeTracker memory_type_tracker_;
 
   THREAD_CHECKER(thread_checker_);
   DISALLOW_COPY_AND_ASSIGN(CommandBufferHelperImpl);
diff --git a/media/gpu/gpu_video_accelerator_util.h b/media/gpu/gpu_video_accelerator_util.h
index 8cf8c12b..c8fe491 100644
--- a/media/gpu/gpu_video_accelerator_util.h
+++ b/media/gpu/gpu_video_accelerator_util.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_GPU_VIDEO_ACCELERATOR_UTIL_H_
 #define MEDIA_GPU_GPU_VIDEO_ACCELERATOR_UTIL_H_
 
-#include <vector>
-
 #include "gpu/config/gpu_info.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/video/video_decode_accelerator.h"
diff --git a/media/gpu/ipc/service/media_gpu_channel.h b/media/gpu/ipc/service/media_gpu_channel.h
index 1fae982c..8d21c2a 100644
--- a/media/gpu/ipc/service/media_gpu_channel.h
+++ b/media/gpu/ipc/service/media_gpu_channel.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_IPC_SERVICE_MEDIA_GPU_CHANNEL_H_
 #define MEDIA_GPU_IPC_SERVICE_MEDIA_GPU_CHANNEL_H_
 
-#include <memory>
-
 #include "base/unguessable_token.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_sender.h"
diff --git a/media/gpu/test/video_player/frame_renderer.h b/media/gpu/test/video_player/frame_renderer.h
index ced5b27a..112e31b 100644
--- a/media/gpu/test/video_player/frame_renderer.h
+++ b/media/gpu/test/video_player/frame_renderer.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_TEST_VIDEO_PLAYER_FRAME_RENDERER_H_
 #define MEDIA_GPU_TEST_VIDEO_PLAYER_FRAME_RENDERER_H_
 
-#include <vector>
-
 #include "base/memory/scoped_refptr.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
diff --git a/media/gpu/v4l2/v4l2_vp8_accelerator.h b/media/gpu/v4l2/v4l2_vp8_accelerator.h
index 8ea3b19..ef0087b9 100644
--- a/media/gpu/v4l2/v4l2_vp8_accelerator.h
+++ b/media/gpu/v4l2/v4l2_vp8_accelerator.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_V4L2_V4L2_VP8_ACCELERATOR_H_
 #define MEDIA_GPU_V4L2_V4L2_VP8_ACCELERATOR_H_
 
-#include <vector>
-
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "media/gpu/vp8_decoder.h"
diff --git a/media/gpu/v4l2/v4l2_vp8_accelerator_legacy.h b/media/gpu/v4l2/v4l2_vp8_accelerator_legacy.h
index 95fb4ad..5920d41 100644
--- a/media/gpu/v4l2/v4l2_vp8_accelerator_legacy.h
+++ b/media/gpu/v4l2/v4l2_vp8_accelerator_legacy.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_V4L2_V4L2_VP8_ACCELERATOR_LEGACY_H_
 #define MEDIA_GPU_V4L2_V4L2_VP8_ACCELERATOR_LEGACY_H_
 
-#include <vector>
-
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "media/gpu/vp8_decoder.h"
diff --git a/media/gpu/v4l2/v4l2_vp9_accelerator_legacy.h b/media/gpu/v4l2/v4l2_vp9_accelerator_legacy.h
index 9e01a27..d93d44f 100644
--- a/media/gpu/v4l2/v4l2_vp9_accelerator_legacy.h
+++ b/media/gpu/v4l2/v4l2_vp9_accelerator_legacy.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_V4L2_V4L2_VP9_ACCELERATOR_LEGACY_H_
 #define MEDIA_GPU_V4L2_V4L2_VP9_ACCELERATOR_LEGACY_H_
 
-#include <vector>
-
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
diff --git a/media/gpu/windows/d3d11_texture_selector.cc b/media/gpu/windows/d3d11_texture_selector.cc
index 7a92a8f..4a84e58 100644
--- a/media/gpu/windows/d3d11_texture_selector.cc
+++ b/media/gpu/windows/d3d11_texture_selector.cc
@@ -66,7 +66,6 @@
     }
     case DXGI_FORMAT_P010: {
       MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder producing P010";
-      output_pixel_format = PIXEL_FORMAT_ARGB;
 
       // TODO(liberato): handle case where we bind P010 directly (see dxva).
 
@@ -94,6 +93,7 @@
           // TODO(liberato): use the format checker, else bind P010.
           MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: 8 bit sRGB";
           output_dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;
+          output_pixel_format = PIXEL_FORMAT_ARGB;
           output_color_space = gfx::ColorSpace::CreateSRGB();
         } else {
           // Bind P010 directly, since we can't copy.
@@ -116,9 +116,10 @@
           output_color_space = gfx::ColorSpace::CreateSCRGBLinear();
         } else if (format_checker->CheckOutputFormatSupport(
                        DXGI_FORMAT_R10G10B10A2_UNORM)) {
-          MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: BGRA10 scRGBLinear";
+          MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: RGB10A2 HDR10/PQ";
           output_dxgi_format = DXGI_FORMAT_R10G10B10A2_UNORM;
-          output_color_space = gfx::ColorSpace::CreateSCRGBLinear();
+          output_pixel_format = PIXEL_FORMAT_XB30;
+          output_color_space = gfx::ColorSpace::CreateHDR10();
         } else {
           // No support at all.  Just bind P010, and hope for the best.
           MEDIA_LOG(INFO, media_log)
@@ -149,9 +150,10 @@
   // If we're trying to produce an output texture that's different from what
   // the decoder is providing, then we need to copy it.  If sharing decoder
   // textures is not allowed, then copy either way.
-  bool needs_texture_copy = !SupportsZeroCopy(gpu_preferences, workarounds) ||
-                            (decoder_output_format != output_dxgi_format) ||
-               base::FeatureList::IsEnabled(kD3D11VideoDecoderAlwaysCopy);
+  bool needs_texture_copy =
+      !SupportsZeroCopy(gpu_preferences, workarounds) ||
+      (decoder_output_format != output_dxgi_format) ||
+      base::FeatureList::IsEnabled(kD3D11VideoDecoderAlwaysCopy);
 
   MEDIA_LOG(INFO, media_log)
       << "D3D11VideoDecoder output color space: "
@@ -178,8 +180,7 @@
     ComD3D11Device device,
     gfx::Size size) {
   // TODO(liberato): If the output format is rgb, then create a pbuffer wrapper.
-  return std::make_unique<DefaultTexture2DWrapper>(size, OutputDXGIFormat(),
-                                                   PixelFormat());
+  return std::make_unique<DefaultTexture2DWrapper>(size, OutputDXGIFormat());
 }
 
 bool TextureSelector::WillCopyForTesting() const {
@@ -228,9 +229,7 @@
     return nullptr;
 
   return std::make_unique<CopyingTexture2DWrapper>(
-      size,
-      std::make_unique<DefaultTexture2DWrapper>(size, OutputDXGIFormat(),
-                                                PixelFormat()),
+      size, std::make_unique<DefaultTexture2DWrapper>(size, OutputDXGIFormat()),
       video_processor_proxy_, out_texture, output_color_space_);
 }
 
diff --git a/media/gpu/windows/d3d11_texture_selector_unittest.cc b/media/gpu/windows/d3d11_texture_selector_unittest.cc
index 12bd030..9ebb9c34 100644
--- a/media/gpu/windows/d3d11_texture_selector_unittest.cc
+++ b/media/gpu/windows/d3d11_texture_selector_unittest.cc
@@ -129,7 +129,7 @@
       CreateWithDefaultGPUInfo(DXGI_FORMAT_P010, ZeroCopyEnabled::kTrue,
                                TextureSelector::HDRMode::kSDROrHDR);
 
-  EXPECT_EQ(tex_sel->PixelFormat(), PIXEL_FORMAT_ARGB);
+  EXPECT_EQ(tex_sel->PixelFormat(), PIXEL_FORMAT_XB30);
   EXPECT_EQ(tex_sel->OutputDXGIFormat(), DXGI_FORMAT_R10G10B10A2_UNORM);
   EXPECT_TRUE(tex_sel->WillCopyForTesting());
 }
diff --git a/media/gpu/windows/d3d11_texture_wrapper.cc b/media/gpu/windows/d3d11_texture_wrapper.cc
index ed2ede8d..0d1da96 100644
--- a/media/gpu/windows/d3d11_texture_wrapper.cc
+++ b/media/gpu/windows/d3d11_texture_wrapper.cc
@@ -22,90 +22,43 @@
 
 namespace {
 
-// Populates Viz |texture_formats| that map to the corresponding DXGI format and
-// VideoPixelFormat. Returns true if they can be successfully mapped.
-bool DXGIFormatToVizFormat(
-    DXGI_FORMAT dxgi_format,
-    VideoPixelFormat pixel_format,
-    size_t textures_per_picture,
-    std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes>& texture_formats) {
+bool SupportsFormat(DXGI_FORMAT dxgi_format) {
   switch (dxgi_format) {
     case DXGI_FORMAT_NV12:
-      DCHECK_EQ(textures_per_picture, 2u);
-      texture_formats[0] = viz::RED_8;  // Y
-      texture_formats[1] = viz::RG_88;  // UV
-      return true;
     case DXGI_FORMAT_P010:
-      // TODO(crbug.com/1011555): P010 formats are not fully supported.
-      // Treat them to be the same as NV12 for the time being.
-      DCHECK_EQ(textures_per_picture, 2u);
-      texture_formats[0] = viz::RED_8;
-      texture_formats[1] = viz::RG_88;
-      return true;
     case DXGI_FORMAT_B8G8R8A8_UNORM:
-      DCHECK_EQ(textures_per_picture, 1u);
-      if (pixel_format != PIXEL_FORMAT_ARGB) {
-        return false;
-      }
-      texture_formats[0] = viz::BGRA_8888;
-      return true;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
     case DXGI_FORMAT_R16G16B16A16_FLOAT:
-      DCHECK_EQ(textures_per_picture, 1u);
-      if (pixel_format != PIXEL_FORMAT_RGBAF16)
-        return false;
-      texture_formats[0] = viz::RGBA_F16;
       return true;
-    default:  // Unsupported
+    default:
       return false;
   }
 }
 
+size_t NumPlanes(DXGI_FORMAT dxgi_format) {
+  switch (dxgi_format) {
+    case DXGI_FORMAT_NV12:
+    case DXGI_FORMAT_P010:
+      return 2;
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+      return 1;
+    default:
+      NOTREACHED();
+      return 0;
+  }
+}
+
 }  // anonymous namespace
 
-// Handy structure so that we can activate / bind one or two textures.
-struct ScopedTextureEverything {
-  ScopedTextureEverything(GLenum unit, GLuint service_id)
-      : active_(unit), binder_(GL_TEXTURE_EXTERNAL_OES, service_id) {}
-  ~ScopedTextureEverything() = default;
-
-  // Order is important; we need |active_| to be constructed first
-  // and destructed last.
-  gl::ScopedActiveTexture active_;
-  gl::ScopedTextureBinder binder_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedTextureEverything);
-};
-
-// Another handy helper class to guarantee that ScopedTextureEverythings
-// are deleted in reverse order.  This is required so that the scoped
-// active texture unit doesn't change.  Surprisingly, none of the stl
-// containers, or the chromium ones, seem to guarantee anything about
-// the order of destruction.
-struct OrderedDestructionList {
-  OrderedDestructionList() = default;
-  ~OrderedDestructionList() {
-    // Erase last-to-first.
-    while (!list_.empty())
-      list_.pop_back();
-  }
-
-  template <typename... Args>
-  void emplace_back(Args&&... args) {
-    list_.emplace_back(std::forward<Args>(args)...);
-  }
-
-  std::list<ScopedTextureEverything> list_;
-  DISALLOW_COPY_AND_ASSIGN(OrderedDestructionList);
-};
-
 Texture2DWrapper::Texture2DWrapper() = default;
 
 Texture2DWrapper::~Texture2DWrapper() = default;
 
 DefaultTexture2DWrapper::DefaultTexture2DWrapper(const gfx::Size& size,
-                                                 DXGI_FORMAT dxgi_format,
-                                                 VideoPixelFormat pixel_format)
-    : size_(size), dxgi_format_(dxgi_format), pixel_format_(pixel_format) {}
+                                                 DXGI_FORMAT dxgi_format)
+    : size_(size), dxgi_format_(dxgi_format) {}
 
 DefaultTexture2DWrapper::~DefaultTexture2DWrapper() = default;
 
@@ -135,28 +88,17 @@
     GetCommandBufferHelperCB get_helper_cb,
     ComD3D11Texture2D texture,
     size_t array_slice) {
-  gpu_resources_ = base::SequenceBound<GpuResources>(
-      std::move(gpu_task_runner),
-      BindToCurrentLoop(base::BindOnce(&DefaultTexture2DWrapper::OnError,
-                                       weak_factory_.GetWeakPtr())));
-
-  const size_t textures_per_picture = VideoFrame::NumPlanes(pixel_format_);
-
-  std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes> texture_formats;
-  if (!DXGIFormatToVizFormat(dxgi_format_, pixel_format_, textures_per_picture,
-                             texture_formats)) {
+  if (!SupportsFormat(dxgi_format_))
     return Status(StatusCode::kUnsupportedTextureFormatForBind);
-  }
 
   // Generate mailboxes and holders.
   // TODO(liberato): Verify that this is really okay off the GPU main thread.
   // The current implementation is.
   std::vector<gpu::Mailbox> mailboxes;
-  for (size_t texture_idx = 0; texture_idx < textures_per_picture;
-       texture_idx++) {
+  for (size_t plane = 0; plane < NumPlanes(dxgi_format_); plane++) {
     mailboxes.push_back(gpu::Mailbox::GenerateForSharedImage());
-    mailbox_holders_[texture_idx] = gpu::MailboxHolder(
-        mailboxes[texture_idx], gpu::SyncToken(), GL_TEXTURE_EXTERNAL_OES);
+    mailbox_holders_[plane] = gpu::MailboxHolder(
+        mailboxes[plane], gpu::SyncToken(), GL_TEXTURE_EXTERNAL_OES);
   }
 
   // Start construction of the GpuResources.
@@ -164,10 +106,12 @@
   // device for decoding.  Sharing seems not to work very well.  Otherwise, we
   // would create the texture with KEYED_MUTEX and NTHANDLE, then send along
   // a handle that we get from |texture| as an IDXGIResource1.
-  gpu_resources_.AsyncCall(&GpuResources::Init)
-      .WithArgs(std::move(get_helper_cb), std::move(mailboxes),
-                GL_TEXTURE_EXTERNAL_OES, size_, textures_per_picture,
-                texture_formats, pixel_format_, texture, array_slice);
+  auto on_error_cb = BindToCurrentLoop(base::BindOnce(
+      &DefaultTexture2DWrapper::OnError, weak_factory_.GetWeakPtr()));
+  gpu_resources_ = base::SequenceBound<GpuResources>(
+      std::move(gpu_task_runner), std::move(on_error_cb),
+      std::move(get_helper_cb), std::move(mailboxes), size_, dxgi_format_,
+      texture, array_slice);
   return OkStatus();
 }
 
@@ -182,217 +126,47 @@
 void DefaultTexture2DWrapper::SetDisplayHDRMetadata(
     const DXGI_HDR_METADATA_HDR10& dxgi_display_metadata) {}
 
-DefaultTexture2DWrapper::GpuResources::GpuResources(OnErrorCB on_error_cb)
-    : on_error_cb_(std::move(on_error_cb)) {}
-
-DefaultTexture2DWrapper::GpuResources::~GpuResources() {
-  if (helper_ && helper_->MakeContextCurrent()) {
-    for (uint32_t service_id : service_ids_)
-      helper_->DestroyTexture(service_id);
-  }
-}
-
-void DefaultTexture2DWrapper::GpuResources::Init(
+DefaultTexture2DWrapper::GpuResources::GpuResources(
+    OnErrorCB on_error_cb,
     GetCommandBufferHelperCB get_helper_cb,
-    const std::vector<gpu::Mailbox> mailboxes,
-    GLenum target,
-    gfx::Size size,
-    size_t textures_per_picture,
-    std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes> texture_formats,
-    VideoPixelFormat pixel_format,
+    const std::vector<gpu::Mailbox>& mailboxes,
+    const gfx::Size& size,
+    DXGI_FORMAT dxgi_format,
     ComD3D11Texture2D texture,
     size_t array_slice) {
   helper_ = get_helper_cb.Run();
 
   if (!helper_ || !helper_->MakeContextCurrent()) {
-    NotifyError(StatusCode::kMakeContextCurrentFailed);
+    std::move(on_error_cb)
+        .Run(std::move(StatusCode::kMakeContextCurrentFailed));
     return;
   }
 
-  // Create the stream for zero-copy use by gl.
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
-  const EGLint stream_attributes[] = {
-      // clang-format off
-      EGL_CONSUMER_LATENCY_USEC_KHR,         0,
-      EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0,
-      EGL_NONE,
-      // clang-format on
-  };
-  EGLStreamKHR stream = eglCreateStreamKHR(egl_display, stream_attributes);
-  if (!stream) {
-    NotifyError(StatusCode::kCreateEglStreamFailed);
+  // Usage flags to allow the display compositor to draw from it, video to
+  // decode, and allow webgl/canvas access.
+  constexpr uint32_t usage =
+      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
+      gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
+      gpu::SHARED_IMAGE_USAGE_SCANOUT;
+
+  auto shared_image_backings =
+      gpu::SharedImageBackingD3D::CreateFromVideoTexture(
+          mailboxes, dxgi_format, size, usage, texture, array_slice);
+  if (shared_image_backings.empty()) {
+    std::move(on_error_cb).Run(std::move(StatusCode::kCreateSharedImageFailed));
     return;
   }
+  DCHECK_EQ(shared_image_backings.size(), NumPlanes(dxgi_format));
 
-  // |stream| will be destroyed when the GLImage is.
-  // TODO(liberato): for tests, it will be destroyed pretty much at the end of
-  // this function unless |helper_| retains it.  Also, this won't work if we
-  // have a FakeCommandBufferHelper since the service IDs aren't meaningful.
-  gl_image_ = base::MakeRefCounted<gl::GLImageDXGI>(size, stream);
-
-  // Create the textures and attach them to the mailboxes.
-  // TODO(liberato): Should we use GL_FLOAT for an fp16 texture?  It doesn't
-  // really seem to matter so far as I can tell.
-  for (size_t texture_idx = 0; texture_idx < textures_per_picture;
-       texture_idx++) {
-    const viz::ResourceFormat format = texture_formats[texture_idx];
-    const GLenum internal_format = viz::GLInternalFormat(format);
-    const GLenum data_type = viz::GLDataType(format);
-    const GLenum data_format = viz::GLDataFormat(format);
-
-    // Adjust the size by the subsampling factor.
-    const size_t width =
-        VideoFrame::Columns(texture_idx, pixel_format, size.width());
-    const size_t height =
-        VideoFrame::Rows(texture_idx, pixel_format, size.height());
-    const gfx::Size plane_size(width, height);
-
-    // TODO(crbug.com/1011555): CreateTexture allocates a GL texture, figure out
-    // if this can be removed.
-    const uint32_t service_id =
-        helper_->CreateTexture(target, internal_format, plane_size.width(),
-                               plane_size.height(), data_format, data_type);
-
-    const auto& mailbox = mailboxes[texture_idx];
-
-    // Shared image does not need to store the colorspace since it is already
-    // stored on the VideoFrame which is provided upon presenting the overlay.
-    // To prevent the developer from mistakenly using it, provide the invalid
-    // value from default-construction.
-    const gfx::ColorSpace kInvalidColorSpace;
-
-    // Usage flags to allow the display compositor to draw from it, video to
-    // decode, and allow webgl/canvas access.
-    const uint32_t shared_image_usage =
-        gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
-        gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
-        gpu::SHARED_IMAGE_USAGE_SCANOUT;
-
-    // Create a shared image
-    // TODO(crbug.com/1011555): Need key shared mutex if shared image is ever
-    // used by another device.
-    scoped_refptr<gpu::gles2::TexturePassthrough> gl_texture =
-        gpu::gles2::TexturePassthrough::CheckedCast(
-            helper_->GetTexture(service_id));
-
-    auto shared_image = std::make_unique<gpu::SharedImageBackingD3D>(
-        mailbox, format, plane_size, kInvalidColorSpace,
-        kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, shared_image_usage,
-        /*swap_chain=*/nullptr, std::move(gl_texture), gl_image_,
-        /*buffer_index=*/0, texture, base::win::ScopedHandle(),
-        /*dxgi_key_mutex=*/nullptr);
-
-    // Caller is assumed to provide cleared d3d textures.
-    shared_image->SetCleared();
-
-    // Shared images will be destroyed when this wrapper goes away.
-    // Only GpuResource can be used to safely destroy the shared images on the
-    // gpu main thread.
-    shared_images_.push_back(helper_->Register(std::move(shared_image)));
-
-    service_ids_.push_back(service_id);
-  }
-
-  // Bind all the textures so that the stream can find them.
-  OrderedDestructionList texture_everythings;
-  for (size_t i = 0; i < textures_per_picture; i++)
-    texture_everythings.emplace_back(GL_TEXTURE0 + i, service_ids_[i]);
-
-  std::vector<EGLAttrib> consumer_attributes;
-  if (textures_per_picture == 2) {
-    // Assume NV12.
-    consumer_attributes = {
-        // clang-format off
-        EGL_COLOR_BUFFER_TYPE,               EGL_YUV_BUFFER_EXT,
-        EGL_YUV_NUMBER_OF_PLANES_EXT,        2,
-        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,      0,
-        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,      1,
-        EGL_NONE,
-        // clang-format on
-    };
-  } else {
-    // Assume some rgb format.
-    consumer_attributes = {
-        // clang-format off
-        EGL_COLOR_BUFFER_TYPE,               EGL_RGB_BUFFER,
-        EGL_NONE,
-        // clang-format on
-    };
-  }
-  EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(
-      egl_display, stream, consumer_attributes.data());
-  if (!result) {
-    NotifyError(StatusCode::kCreateEglStreamConsumerFailed);
-    return;
-  }
-
-  EGLAttrib producer_attributes[] = {
-      EGL_NONE,
-  };
-
-  result = eglCreateStreamProducerD3DTextureANGLE(egl_display, stream,
-                                                  producer_attributes);
-  if (!result) {
-    NotifyError(StatusCode::kCreateEglStreamProducerFailed);
-    return;
-  }
-
-  // Note that this is valid as long as |gl_image_| is valid; it is
-  // what deletes the stream.
-  stream_ = stream;
-
-  // Bind the image to each texture.
-  for (size_t texture_idx = 0; texture_idx < service_ids_.size();
-       texture_idx++) {
-    helper_->BindImage(service_ids_[texture_idx], gl_image_.get(),
-                       false /* client_managed */);
-  }
-
-  // Specify the texture so ProcessTexture knows how to process it using a GL
-  // image.
-  gl_image_->SetTexture(texture, array_slice);
-
-  PushNewTexture();
+  for (auto& backing : shared_image_backings)
+    shared_images_.push_back(helper_->Register(std::move(backing)));
 }
 
-void DefaultTexture2DWrapper::GpuResources::PushNewTexture() {
-  // If init didn't complete, then signal (another) error that will probably be
-  // ignored in favor of whatever we signalled earlier.
-  if (!gl_image_ || !stream_) {
-    NotifyError(StatusCode::kDecoderInitializeNeverCompleted);
+DefaultTexture2DWrapper::GpuResources::~GpuResources() {
+  // Destroy shared images with a current context.
+  if (!helper_ || !helper_->MakeContextCurrent())
     return;
-  }
-
-  if (!helper_ || !helper_->MakeContextCurrent()) {
-    NotifyError(StatusCode::kMakeContextCurrentFailed);
-    return;
-  }
-
-  // Notify angle that it has a new texture.
-  EGLAttrib frame_attributes[] = {
-      EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE,
-      gl_image_->level(),
-      EGL_NONE,
-  };
-
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
-  if (!eglStreamPostD3DTextureANGLE(
-          egl_display, stream_, static_cast<void*>(gl_image_->texture().Get()),
-          frame_attributes)) {
-    NotifyError(StatusCode::kPostTextureFailed);
-    return;
-  }
-
-  if (!eglStreamConsumerAcquireKHR(egl_display, stream_)) {
-    NotifyError(StatusCode::kPostAcquireStreamFailed);
-    return;
-  }
-}
-
-void DefaultTexture2DWrapper::GpuResources::NotifyError(Status status) {
-  if (on_error_cb_)
-    std::move(on_error_cb_).Run(std::move(status));
-  // else this isn't the first error, so skip it.
+  shared_images_.clear();
 }
 
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_texture_wrapper.h b/media/gpu/windows/d3d11_texture_wrapper.h
index 5c097ec9..9604e88 100644
--- a/media/gpu/windows/d3d11_texture_wrapper.h
+++ b/media/gpu/windows/d3d11_texture_wrapper.h
@@ -74,9 +74,7 @@
 
   // While the specific texture instance can change on every call to
   // ProcessTexture, the dxgi format must be the same for all of them.
-  DefaultTexture2DWrapper(const gfx::Size& size,
-                          DXGI_FORMAT dxgi_format,
-                          VideoPixelFormat pixel_format);
+  DefaultTexture2DWrapper(const gfx::Size& size, DXGI_FORMAT dxgi_format);
   ~DefaultTexture2DWrapper() override;
 
   Status Init(scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
@@ -99,36 +97,17 @@
   // can use the mailbox.
   class GpuResources {
    public:
-    GpuResources(OnErrorCB on_error_cb);
+    GpuResources(OnErrorCB on_error_cb,
+                 GetCommandBufferHelperCB get_helper_cb,
+                 const std::vector<gpu::Mailbox>& mailboxes,
+                 const gfx::Size& size,
+                 DXGI_FORMAT dxgi_format,
+                 ComD3D11Texture2D texture,
+                 size_t array_slice);
     ~GpuResources();
 
-    void Init(
-        GetCommandBufferHelperCB get_helper_cb,
-        const std::vector<gpu::Mailbox> mailboxes,
-        GLenum target,
-        gfx::Size size,
-        size_t textures_per_picture,
-        std::array<viz::ResourceFormat, VideoFrame::kMaxPlanes> texture_formats,
-        VideoPixelFormat pixel_format,
-        ComD3D11Texture2D texture,
-        size_t array_slice);
-
-    std::vector<uint32_t> service_ids_;
-
    private:
-    // Push a new |texture|, |array_slice| to |gl_image_|.
-    // Both |texture| and |array_slice| were set by Init.
-    void PushNewTexture();
-
-    // Notify our wrapper about |status|, if we haven't before.
-    void NotifyError(Status status);
-
-    // May be empty if we've already sent an error.
-    OnErrorCB on_error_cb_;
-
     scoped_refptr<CommandBufferHelper> helper_;
-    scoped_refptr<gl::GLImageDXGI> gl_image_;
-    EGLStreamKHR stream_;
 
     std::vector<std::unique_ptr<gpu::SharedImageRepresentationFactoryRef>>
         shared_images_;
@@ -146,7 +125,6 @@
   base::SequenceBound<GpuResources> gpu_resources_;
   MailboxHolderArray mailbox_holders_;
   DXGI_FORMAT dxgi_format_;
-  VideoPixelFormat pixel_format_;
 
   base::WeakPtrFactory<DefaultTexture2DWrapper> weak_factory_{this};
 };
diff --git a/media/gpu/windows/d3d11_texture_wrapper_unittest.cc b/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
index 3142782..cfcd5fa 100644
--- a/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
+++ b/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
@@ -88,10 +88,8 @@
 TEST_F(D3D11TextureWrapperUnittest, NV12InitSucceeds) {
   STOP_IF_WIN7();
   const DXGI_FORMAT dxgi_format = DXGI_FORMAT_NV12;
-  const VideoPixelFormat pixel_format = PIXEL_FORMAT_NV12;
 
-  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format,
-                                                           pixel_format);
+  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format);
   const Status init_result = wrapper->Init(
       task_runner_, get_helper_cb_, /*texture_d3d=*/nullptr, /*array_slice=*/0);
   EXPECT_TRUE(init_result.is_ok());
@@ -102,10 +100,8 @@
 TEST_F(D3D11TextureWrapperUnittest, BGRA8InitSucceeds) {
   STOP_IF_WIN7();
   const DXGI_FORMAT dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;
-  const VideoPixelFormat pixel_format = PIXEL_FORMAT_ARGB;
 
-  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format,
-                                                           pixel_format);
+  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format);
   const Status init_result = wrapper->Init(
       task_runner_, get_helper_cb_, /*texture_d3d=*/nullptr, /*array_slice=*/0);
   EXPECT_TRUE(init_result.is_ok());
@@ -114,10 +110,8 @@
 TEST_F(D3D11TextureWrapperUnittest, FP16InitSucceeds) {
   STOP_IF_WIN7();
   const DXGI_FORMAT dxgi_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
-  const VideoPixelFormat pixel_format = PIXEL_FORMAT_RGBAF16;
 
-  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format,
-                                                           pixel_format);
+  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format);
   const Status init_result = wrapper->Init(
       task_runner_, get_helper_cb_, /*texture_d3d=*/nullptr, /*array_slice=*/0);
   EXPECT_TRUE(init_result.is_ok());
@@ -126,10 +120,8 @@
 TEST_F(D3D11TextureWrapperUnittest, P010InitSucceeds) {
   STOP_IF_WIN7();
   const DXGI_FORMAT dxgi_format = DXGI_FORMAT_P010;
-  const VideoPixelFormat pixel_format = PIXEL_FORMAT_NV12;
 
-  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format,
-                                                           pixel_format);
+  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format);
   const Status init_result = wrapper->Init(
       task_runner_, get_helper_cb_, /*texture_d3d=*/nullptr, /*array_slice=*/0);
   EXPECT_TRUE(init_result.is_ok());
@@ -138,13 +130,11 @@
 TEST_F(D3D11TextureWrapperUnittest, UnknownInitFails) {
   STOP_IF_WIN7();
   const DXGI_FORMAT dxgi_format = DXGI_FORMAT_UNKNOWN;
-  const VideoPixelFormat pixel_format = PIXEL_FORMAT_UNKNOWN;
 
-  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format,
-                                                           pixel_format);
+  auto wrapper = std::make_unique<DefaultTexture2DWrapper>(size_, dxgi_format);
   const Status init_result = wrapper->Init(
       task_runner_, get_helper_cb_, /*texture_d3d=*/nullptr, /*array_slice=*/0);
   EXPECT_FALSE(init_result.is_ok());
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/gpu/windows/d3d11_video_device_format_support.h b/media/gpu/windows/d3d11_video_device_format_support.h
index 0e07a4ec..87ac2b1 100644
--- a/media/gpu/windows/d3d11_video_device_format_support.h
+++ b/media/gpu/windows/d3d11_video_device_format_support.h
@@ -6,7 +6,6 @@
 #define MEDIA_GPU_WINDOWS_D3D11_VIDEO_DEVICE_FORMAT_SUPPORT_H_
 
 #include <d3d11_1.h>
-#include <vector>
 
 #include "base/optional.h"
 #include "media/base/media_log.h"
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index 19736f5..e755197 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -2747,16 +2747,12 @@
       // this |picture_buffer| will be updated when the video frame is created.
       const auto& mailbox = gpu::Mailbox::GenerateForSharedImage();
 
-      auto shared_image = std::make_unique<gpu::SharedImageBackingD3D>(
+      auto shared_image = gpu::SharedImageBackingD3D::CreateFromGLTexture(
           mailbox, viz_formats[texture_idx],
           picture_buffer->texture_size(texture_idx),
           picture_buffer->color_space(), kTopLeft_GrSurfaceOrigin,
-          kPremul_SkAlphaType, shared_image_usage,
-          /*swap_chain=*/nullptr, std::move(gl_texture),
-          picture_buffer->gl_image(),
-          /*buffer_index=*/0, gl_image_dxgi->texture(),
-          base::win::ScopedHandle(),
-          /*dxgi_keyed_mutex=*/nullptr);
+          kPremul_SkAlphaType, shared_image_usage, gl_image_dxgi->texture(),
+          std::move(gl_texture));
 
       // Caller is assumed to provide cleared d3d textures.
       shared_image->SetCleared();
diff --git a/media/learning/impl/feature_provider.h b/media/learning/impl/feature_provider.h
index 3932905..c53fb81 100644
--- a/media/learning/impl/feature_provider.h
+++ b/media/learning/impl/feature_provider.h
@@ -5,9 +5,6 @@
 #ifndef MEDIA_LEARNING_IMPL_FEATURE_PROVIDER_H_
 #define MEDIA_LEARNING_IMPL_FEATURE_PROVIDER_H_
 
-#include <map>
-#include <string>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
diff --git a/media/learning/impl/fisher_iris_dataset.h b/media/learning/impl/fisher_iris_dataset.h
index 09d7c81..6117ddf5 100644
--- a/media/learning/impl/fisher_iris_dataset.h
+++ b/media/learning/impl/fisher_iris_dataset.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_LEARNING_IMPL_FISHER_IRIS_DATASET_H_
 #define MEDIA_LEARNING_IMPL_FISHER_IRIS_DATASET_H_
 
-#include <vector>
-
 #include "base/memory/ref_counted.h"
 #include "media/learning/common/labelled_example.h"
 
diff --git a/media/learning/impl/lookup_table_trainer.h b/media/learning/impl/lookup_table_trainer.h
index 5417c848..f17e617 100644
--- a/media/learning/impl/lookup_table_trainer.h
+++ b/media/learning/impl/lookup_table_trainer.h
@@ -5,9 +5,6 @@
 #ifndef MEDIA_LEARNING_IMPL_LOOKUP_TABLE_TRAINER_H_
 #define MEDIA_LEARNING_IMPL_LOOKUP_TABLE_TRAINER_H_
 
-#include <memory>
-#include <vector>
-
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "media/learning/common/learning_task.h"
diff --git a/media/learning/impl/random_number_generator.h b/media/learning/impl/random_number_generator.h
index e4ee413..aeb4514 100644
--- a/media/learning/impl/random_number_generator.h
+++ b/media/learning/impl/random_number_generator.h
@@ -6,7 +6,6 @@
 #define MEDIA_LEARNING_IMPL_RANDOM_NUMBER_GENERATOR_H_
 
 #include <cstdint>
-#include <memory>
 
 #include "base/component_export.h"
 #include "base/macros.h"
diff --git a/media/learning/mojo/public/cpp/mojo_learning_task_controller.h b/media/learning/mojo/public/cpp/mojo_learning_task_controller.h
index efe5067..f44d220 100644
--- a/media/learning/mojo/public/cpp/mojo_learning_task_controller.h
+++ b/media/learning/mojo/public/cpp/mojo_learning_task_controller.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_LEARNING_MOJO_PUBLIC_CPP_MOJO_LEARNING_TASK_CONTROLLER_H_
 #define MEDIA_LEARNING_MOJO_PUBLIC_CPP_MOJO_LEARNING_TASK_CONTROLLER_H_
 
-#include <utility>
-
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "media/learning/common/learning_task_controller.h"
diff --git a/media/midi/usb_midi_device_factory_android.h b/media/midi/usb_midi_device_factory_android.h
index d0dae559a..feb6faf 100644
--- a/media/midi/usb_midi_device_factory_android.h
+++ b/media/midi/usb_midi_device_factory_android.h
@@ -6,7 +6,6 @@
 #define MEDIA_MIDI_USB_MIDI_DEVICE_FACTORY_ANDROID_H_
 
 #include <jni.h>
-#include <vector>
 
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
diff --git a/media/mojo/common/mojo_data_pipe_read_write.h b/media/mojo/common/mojo_data_pipe_read_write.h
index 85b767c..553d356 100644
--- a/media/mojo/common/mojo_data_pipe_read_write.h
+++ b/media/mojo/common/mojo_data_pipe_read_write.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_MOJO_COMMON_MOJO_DATA_PIPE_READ_WRITE_H_
 #define MEDIA_MOJO_COMMON_MOJO_DATA_PIPE_READ_WRITE_H_
 
-#include <memory>
-
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "mojo/public/cpp/system/data_pipe.h"
diff --git a/media/mojo/mojom/pipeline_status_mojom_traits.h b/media/mojo/mojom/pipeline_status_mojom_traits.h
index d2893e2..220323d7 100644
--- a/media/mojo/mojom/pipeline_status_mojom_traits.h
+++ b/media/mojo/mojom/pipeline_status_mojom_traits.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_MOJO_MOJOM_PIPELINE_STATUS_MOJOM_TRAITS_H_
 #define MEDIA_MOJO_MOJOM_PIPELINE_STATUS_MOJOM_TRAITS_H_
 
-#include <string>
-
 #include "media/base/pipeline_status.h"
 #include "media/mojo/mojom/media_types.mojom.h"
 
diff --git a/media/mojo/services/video_decode_stats_recorder.h b/media/mojo/services/video_decode_stats_recorder.h
index 72bbd0a..c595fba 100644
--- a/media/mojo/services/video_decode_stats_recorder.h
+++ b/media/mojo/services/video_decode_stats_recorder.h
@@ -6,7 +6,6 @@
 #define MEDIA_MOJO_SERVICES_VIDEO_DECODE_STATS_RECORDER_H_
 
 #include <stdint.h>
-#include <string>
 
 #include "base/time/time.h"
 #include "media/base/video_codecs.h"
diff --git a/net/disk_cache/blockfile/backend_impl.cc b/net/disk_cache/blockfile/backend_impl.cc
index d056c4a..ff387412 100644
--- a/net/disk_cache/blockfile/backend_impl.cc
+++ b/net/disk_cache/blockfile/backend_impl.cc
@@ -1997,9 +1997,6 @@
 
   stats_.ResetRatios();
   stats_.SetCounter(Stats::TRIM_ENTRY, 0);
-
-  if (GetCacheType() == net::DISK_CACHE)
-    block_files_.ReportStats();
 }
 
 void BackendImpl::UpgradeTo2_1() {
diff --git a/net/disk_cache/blockfile/block_files.cc b/net/disk_cache/blockfile/block_files.cc
index bc9404a..e9375270 100644
--- a/net/disk_cache/blockfile/block_files.cc
+++ b/net/disk_cache/blockfile/block_files.cc
@@ -384,24 +384,6 @@
   block_files_.clear();
 }
 
-void BlockFiles::ReportStats() {
-  DCHECK(thread_checker_->CalledOnValidThread());
-  int used_blocks[kFirstAdditionalBlockFile];
-  int load[kFirstAdditionalBlockFile];
-  for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
-    GetFileStats(i, &used_blocks[i], &load[i]);
-  }
-  UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_0", used_blocks[0]);
-  UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_1", used_blocks[1]);
-  UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_2", used_blocks[2]);
-  UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_3", used_blocks[3]);
-
-  UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
-  UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
-  UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
-  UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
-}
-
 bool BlockFiles::IsValid(Addr address) {
 #ifdef NDEBUG
   return true;
@@ -678,37 +660,6 @@
   return true;
 }
 
-// We are interested in the total number of blocks used by this file type, and
-// the max number of blocks that we can store (reported as the percentage of
-// used blocks). In order to find out the number of used blocks, we have to
-// substract the empty blocks from the total blocks for each file in the chain.
-void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
-  int max_blocks = 0;
-  *used_count = 0;
-  *load = 0;
-  for (;;) {
-    if (!block_files_[index] && !OpenBlockFile(index))
-      return;
-
-    BlockFileHeader* header =
-        reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
-
-    max_blocks += header->max_entries;
-    int used = header->max_entries;
-    for (int i = 0; i < kMaxNumBlocks; i++) {
-      used -= header->empty[i] * (i + 1);
-      DCHECK_GE(used, 0);
-    }
-    *used_count += used;
-
-    if (!header->next_file)
-      break;
-    index = header->next_file;
-  }
-  if (max_blocks)
-    *load = *used_count * 100 / max_blocks;
-}
-
 base::FilePath BlockFiles::Name(int index) {
   // The file format allows for 256 files.
   DCHECK(index < 256 && index >= 0);
diff --git a/net/disk_cache/blockfile/block_files.h b/net/disk_cache/blockfile/block_files.h
index f74ef28..944d55e2 100644
--- a/net/disk_cache/blockfile/block_files.h
+++ b/net/disk_cache/blockfile/block_files.h
@@ -117,9 +117,6 @@
   // cache is being purged.
   void CloseFiles();
 
-  // Sends UMA stats.
-  void ReportStats();
-
   // Returns true if the blocks pointed by a given address are currently used.
   // This method is only intended for debugging.
   bool IsValid(Addr address);
@@ -147,9 +144,6 @@
   // Restores the header of a potentially inconsistent file.
   bool FixBlockFileHeader(MappedFile* file);
 
-  // Retrieves stats for the given file index.
-  void GetFileStats(int index, int* used_count, int* load);
-
   // Returns the filename for a given file index.
   base::FilePath Name(int index);
 
diff --git a/net/disk_cache/blockfile/block_files_unittest.cc b/net/disk_cache/blockfile/block_files_unittest.cc
index cb0fb75..f674383 100644
--- a/net/disk_cache/blockfile/block_files_unittest.cc
+++ b/net/disk_cache/blockfile/block_files_unittest.cc
@@ -285,27 +285,6 @@
   EXPECT_TRUE(nullptr == files.GetFile(addr));
 }
 
-// Tests that we generate the correct file stats.
-TEST_F(DiskCacheTest, BlockFiles_Stats) {
-  ASSERT_TRUE(CopyTestCache("remove_load1"));
-
-  BlockFiles files(cache_path_);
-  ASSERT_TRUE(files.Init(false));
-  int used, load;
-
-  files.GetFileStats(0, &used, &load);
-  EXPECT_EQ(101, used);
-  EXPECT_EQ(9, load);
-
-  files.GetFileStats(1, &used, &load);
-  EXPECT_EQ(203, used);
-  EXPECT_EQ(19, load);
-
-  files.GetFileStats(2, &used, &load);
-  EXPECT_EQ(0, used);
-  EXPECT_EQ(0, load);
-}
-
 // Tests that we add and remove blocks correctly.
 TEST_F(DiskCacheTest, AllocationMap) {
   ASSERT_TRUE(CleanupCacheDir());
diff --git a/remoting/host/policy_watcher.cc b/remoting/host/policy_watcher.cc
index 97507fb..f0ee01a 100644
--- a/remoting/host/policy_watcher.cc
+++ b/remoting/host/policy_watcher.cc
@@ -313,7 +313,7 @@
   while (!iter.IsAtEnd()) {
     base::Value* old_policy;
     if (!(effective_policies_->Get(iter.key(), &old_policy) &&
-          old_policy->Equals(&iter.value()))) {
+          *old_policy == iter.value())) {
       changed_policies->Set(
           iter.key(), base::Value::ToUniquePtrValue(iter.value().Clone()));
     }
diff --git a/services/metrics/public/cpp/ukm_source_id.cc b/services/metrics/public/cpp/ukm_source_id.cc
index 25118ed9a..f1e748ee 100644
--- a/services/metrics/public/cpp/ukm_source_id.cc
+++ b/services/metrics/public/cpp/ukm_source_id.cc
@@ -82,4 +82,11 @@
   return ukm::SourceIdObj::FromInt64(source_id).GetType();
 }
 
+SourceId NoURLSourceId() {
+  static const SourceId source_id =
+      SourceIdObj::FromOtherId(AssignNewSourceId(), SourceIdType::NO_URL_ID)
+          .ToInt64();
+  return source_id;
+}
+
 }  // namespace ukm
diff --git a/services/metrics/public/cpp/ukm_source_id.h b/services/metrics/public/cpp/ukm_source_id.h
index fcea73e..8a70212 100644
--- a/services/metrics/public/cpp/ukm_source_id.h
+++ b/services/metrics/public/cpp/ukm_source_id.h
@@ -66,7 +66,12 @@
     // shared workers and service workers). Shared workers and service workers
     // can be connected to multiple clients (e.g. documents or other workers).
     WORKER_ID = 7,
-    kMaxValue = WORKER_ID,
+    // Source ID type for metrics that doesn't need to be associated with a
+    // specific URL. Metrics with this type will be whitelisted and always
+    // recorded. A source ID of this type can be obtained with NoURLSourceId().
+    NO_URL_ID = 8,
+
+    kMaxValue = NO_URL_ID,
   };
 
   // Default constructor has the invalid value.
@@ -119,6 +124,9 @@
 METRICS_EXPORT SourceId ConvertToSourceId(int64_t other_id,
                                           SourceIdType id_type);
 
+// Utility for getting source ID with NO_URL_ID type.
+METRICS_EXPORT SourceId NoURLSourceId();
+
 // Get the SourceIdType of the SourceId object.
 METRICS_EXPORT SourceIdType GetSourceIdType(SourceId source_id);
 
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index c7fd00f..e589f9a 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -121,8 +121,6 @@
     "test/paths.cc",
     "test/paths.h",
     "test/run_all_unittests.cc",
-    "test/sql_test_base.cc",
-    "test/sql_test_base.h",
     "test/sql_test_suite.cc",
     "test/sql_test_suite.h",
     "transaction_unittest.cc",
diff --git a/sql/database_unittest.cc b/sql/database_unittest.cc
index 9ae6a17..ace0362 100644
--- a/sql/database_unittest.cc
+++ b/sql/database_unittest.cc
@@ -2,21 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "sql/database.h"
+
 #include <stddef.h>
 #include <stdint.h>
 
 #include "base/bind.h"
 #include "base/files/file_util.h"
-#include "base/files/scoped_file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/trace_event/process_memory_dump.h"
 #include "build/build_config.h"
-#include "sql/database.h"
 #include "sql/database_memory_dump_provider.h"
 #include "sql/meta_table.h"
 #include "sql/sql_features.h"
@@ -24,7 +23,6 @@
 #include "sql/test/database_test_peer.h"
 #include "sql/test/error_callback_support.h"
 #include "sql/test/scoped_error_expecter.h"
-#include "sql/test/sql_test_base.h"
 #include "sql/test/test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/sqlite/sqlite3.h"
@@ -37,9 +35,9 @@
 
 // Helper to return the count of items in sqlite_master.  Return -1 in
 // case of error.
-int SqliteMasterCount(sql::Database* db) {
+int SqliteMasterCount(Database* db) {
   const char* kMasterCount = "SELECT COUNT(*) FROM sqlite_master";
-  sql::Statement s(db->GetUniqueStatement(kMasterCount));
+  Statement s(db->GetUniqueStatement(kMasterCount));
   return s.Step() ? s.ColumnInt(0) : -1;
 }
 
@@ -48,7 +46,7 @@
 // explicitly having the ref count live longer than the object.
 class RefCounter {
  public:
-  RefCounter(size_t* counter) : counter_(counter) { (*counter_)++; }
+  explicit RefCounter(size_t* counter) : counter_(counter) { (*counter_)++; }
   RefCounter(const RefCounter& other) : counter_(other.counter_) {
     (*counter_)++;
   }
@@ -61,24 +59,24 @@
 };
 
 // Empty callback for implementation of ErrorCallbackSetHelper().
-void IgnoreErrorCallback(int error, sql::Statement* stmt) {}
+void IgnoreErrorCallback(int error, Statement* stmt) {}
 
-void ErrorCallbackSetHelper(sql::Database* db,
+void ErrorCallbackSetHelper(Database* db,
                             size_t* counter,
                             const RefCounter& r,
                             int error,
-                            sql::Statement* stmt) {
+                            Statement* stmt) {
   // The ref count should not go to zero when changing the callback.
   EXPECT_GT(*counter, 0u);
   db->set_error_callback(base::BindRepeating(&IgnoreErrorCallback));
   EXPECT_GT(*counter, 0u);
 }
 
-void ErrorCallbackResetHelper(sql::Database* db,
+void ErrorCallbackResetHelper(Database* db,
                               size_t* counter,
                               const RefCounter& r,
                               int error,
-                              sql::Statement* stmt) {
+                              Statement* stmt) {
   // The ref count should not go to zero when clearing the callback.
   EXPECT_GT(*counter, 0u);
   db->reset_error_callback();
@@ -86,10 +84,10 @@
 }
 
 // Handle errors by blowing away the database.
-void RazeErrorCallback(sql::Database* db,
+void RazeErrorCallback(Database* db,
                        int expected_error,
                        int error,
-                       sql::Statement* stmt) {
+                       Statement* stmt) {
   // Nothing here needs extended errors at this time.
   EXPECT_EQ(expected_error, expected_error & 0xff);
   EXPECT_EQ(expected_error, error & 0xff);
@@ -106,23 +104,36 @@
   }
   ~ScopedUmaskSetter() { umask(old_umask_); }
 
+  ScopedUmaskSetter(const ScopedUmaskSetter&) = delete;
+  ScopedUmaskSetter& operator=(const ScopedUmaskSetter&) = delete;
+
  private:
   mode_t old_umask_;
-  DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedUmaskSetter);
 };
 #endif  // defined(OS_POSIX)
 
 }  // namespace
 
 // We use the parameter to run all tests with WAL mode on and off.
-class SQLDatabaseTest : public SQLTestBase,
+class SQLDatabaseTest : public testing::Test,
                         public testing::WithParamInterface<bool> {
  public:
-  SQLDatabaseTest() : SQLTestBase(GetDBOptions()) {}
-  explicit SQLDatabaseTest(DatabaseOptions options) : SQLTestBase(options) {}
+  enum class OverwriteType {
+    kTruncate,
+    kOverwrite,
+  };
 
-  sql::DatabaseOptions GetDBOptions() {
-    sql::DatabaseOptions options;
+  SQLDatabaseTest() : SQLDatabaseTest(GetDBOptions()) {}
+  ~SQLDatabaseTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    db_path_ = temp_dir_.GetPath().AppendASCII("recovery_test.sqlite");
+    ASSERT_TRUE(db_.Open(db_path_));
+  }
+
+  DatabaseOptions GetDBOptions() {
+    DatabaseOptions options;
     options.wal_mode = IsWALEnabled();
     // TODO(crbug.com/1120969): Remove after switching to exclusive mode on by
     // default.
@@ -135,186 +146,213 @@
 #endif  // defined(OS_FUCHSIA)
     return options;
   }
+
   bool IsWALEnabled() { return GetParam(); }
+
+  bool TruncateDatabase() {
+    base::File file(db_path_,
+                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    return file.SetLength(0);
+  }
+
+  bool OverwriteDatabaseHeader(OverwriteType type) {
+    base::File file(db_path_,
+                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    if (type == OverwriteType::kTruncate) {
+      if (!file.SetLength(0))
+        return false;
+    }
+
+    static constexpr char kText[] = "Now is the winter of our discontent.";
+    constexpr int kTextBytes = sizeof(kText) - 1;
+    return file.Write(0, kText, kTextBytes) == kTextBytes;
+  }
+
+ protected:
+  explicit SQLDatabaseTest(DatabaseOptions options) : db_(options) {}
+
+  base::ScopedTempDir temp_dir_;
+  base::FilePath db_path_;
+  Database db_;
 };
 
 TEST_P(SQLDatabaseTest, Execute) {
   // Valid statement should return true.
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  EXPECT_EQ(SQLITE_OK, db().GetErrorCode());
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_EQ(SQLITE_OK, db_.GetErrorCode());
 
   // Invalid statement should fail.
   ASSERT_EQ(SQLITE_ERROR,
-            db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b"));
-  EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode());
+            db_.ExecuteAndReturnErrorCode("CREATE TAB foo (a, b"));
+  EXPECT_EQ(SQLITE_ERROR, db_.GetErrorCode());
 }
 
 TEST_P(SQLDatabaseTest, ExecuteWithErrorCode) {
   ASSERT_EQ(SQLITE_OK,
-            db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
-  ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
-  ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(
+            db_.ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
+  ASSERT_EQ(SQLITE_ERROR, db_.ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
+  ASSERT_EQ(SQLITE_ERROR, db_.ExecuteAndReturnErrorCode(
                               "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
 }
 
 TEST_P(SQLDatabaseTest, CachedStatement) {
-  sql::StatementID id1 = SQL_FROM_HERE;
-  sql::StatementID id2 = SQL_FROM_HERE;
+  StatementID id1 = SQL_FROM_HERE;
+  StatementID id2 = SQL_FROM_HERE;
   static const char kId1Sql[] = "SELECT a FROM foo";
   static const char kId2Sql[] = "SELECT b FROM foo";
 
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
 
   sqlite3_stmt* raw_id1_statement;
   sqlite3_stmt* raw_id2_statement;
   {
-    scoped_refptr<sql::Database::StatementRef> ref_from_id1 =
-        db().GetCachedStatement(id1, kId1Sql);
+    scoped_refptr<Database::StatementRef> ref_from_id1 =
+        db_.GetCachedStatement(id1, kId1Sql);
     raw_id1_statement = ref_from_id1->stmt();
 
-    sql::Statement from_id1(std::move(ref_from_id1));
+    Statement from_id1(std::move(ref_from_id1));
     ASSERT_TRUE(from_id1.is_valid());
     ASSERT_TRUE(from_id1.Step());
     EXPECT_EQ(12, from_id1.ColumnInt(0));
 
-    scoped_refptr<sql::Database::StatementRef> ref_from_id2 =
-        db().GetCachedStatement(id2, kId2Sql);
+    scoped_refptr<Database::StatementRef> ref_from_id2 =
+        db_.GetCachedStatement(id2, kId2Sql);
     raw_id2_statement = ref_from_id2->stmt();
     EXPECT_NE(raw_id1_statement, raw_id2_statement);
 
-    sql::Statement from_id2(std::move(ref_from_id2));
+    Statement from_id2(std::move(ref_from_id2));
     ASSERT_TRUE(from_id2.is_valid());
     ASSERT_TRUE(from_id2.Step());
     EXPECT_EQ(13, from_id2.ColumnInt(0));
   }
 
   {
-    scoped_refptr<sql::Database::StatementRef> ref_from_id1 =
-        db().GetCachedStatement(id1, kId1Sql);
+    scoped_refptr<Database::StatementRef> ref_from_id1 =
+        db_.GetCachedStatement(id1, kId1Sql);
     EXPECT_EQ(raw_id1_statement, ref_from_id1->stmt())
         << "statement was not cached";
 
-    sql::Statement from_id1(std::move(ref_from_id1));
+    Statement from_id1(std::move(ref_from_id1));
     ASSERT_TRUE(from_id1.is_valid());
     ASSERT_TRUE(from_id1.Step()) << "cached statement was not reset";
     EXPECT_EQ(12, from_id1.ColumnInt(0));
 
-    scoped_refptr<sql::Database::StatementRef> ref_from_id2 =
-        db().GetCachedStatement(id2, kId2Sql);
+    scoped_refptr<Database::StatementRef> ref_from_id2 =
+        db_.GetCachedStatement(id2, kId2Sql);
     EXPECT_EQ(raw_id2_statement, ref_from_id2->stmt())
         << "statement was not cached";
 
-    sql::Statement from_id2(std::move(ref_from_id2));
+    Statement from_id2(std::move(ref_from_id2));
     ASSERT_TRUE(from_id2.is_valid());
     ASSERT_TRUE(from_id2.Step()) << "cached statement was not reset";
     EXPECT_EQ(13, from_id2.ColumnInt(0));
   }
 
-  EXPECT_DCHECK_DEATH(db().GetCachedStatement(id1, kId2Sql))
+  EXPECT_DCHECK_DEATH(db_.GetCachedStatement(id1, kId2Sql))
       << "Using a different SQL with the same statement ID should DCHECK";
-  EXPECT_DCHECK_DEATH(db().GetCachedStatement(id2, kId1Sql))
+  EXPECT_DCHECK_DEATH(db_.GetCachedStatement(id2, kId1Sql))
       << "Using a different SQL with the same statement ID should DCHECK";
 }
 
 TEST_P(SQLDatabaseTest, IsSQLValidTest) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo"));
-  ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.IsSQLValid("SELECT a FROM foo"));
+  ASSERT_FALSE(db_.IsSQLValid("SELECT no_exist FROM foo"));
 }
 
 TEST_P(SQLDatabaseTest, DoesTableExist) {
-  EXPECT_FALSE(db().DoesTableExist("foo"));
-  EXPECT_FALSE(db().DoesTableExist("foo_index"));
+  EXPECT_FALSE(db_.DoesTableExist("foo"));
+  EXPECT_FALSE(db_.DoesTableExist("foo_index"));
 
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("CREATE INDEX foo_index ON foo (a)"));
-  EXPECT_TRUE(db().DoesTableExist("foo"));
-  EXPECT_FALSE(db().DoesTableExist("foo_index"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("CREATE INDEX foo_index ON foo (a)"));
+  EXPECT_TRUE(db_.DoesTableExist("foo"));
+  EXPECT_FALSE(db_.DoesTableExist("foo_index"));
 
   // DoesTableExist() is case-sensitive.
-  EXPECT_FALSE(db().DoesTableExist("Foo"));
-  EXPECT_FALSE(db().DoesTableExist("FOO"));
+  EXPECT_FALSE(db_.DoesTableExist("Foo"));
+  EXPECT_FALSE(db_.DoesTableExist("FOO"));
 }
 
 TEST_P(SQLDatabaseTest, DoesIndexExist) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  EXPECT_FALSE(db().DoesIndexExist("foo"));
-  EXPECT_FALSE(db().DoesIndexExist("foo_ubdex"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_FALSE(db_.DoesIndexExist("foo"));
+  EXPECT_FALSE(db_.DoesIndexExist("foo_ubdex"));
 
-  ASSERT_TRUE(db().Execute("CREATE INDEX foo_index ON foo (a)"));
-  EXPECT_TRUE(db().DoesIndexExist("foo_index"));
-  EXPECT_FALSE(db().DoesIndexExist("foo"));
+  ASSERT_TRUE(db_.Execute("CREATE INDEX foo_index ON foo (a)"));
+  EXPECT_TRUE(db_.DoesIndexExist("foo_index"));
+  EXPECT_FALSE(db_.DoesIndexExist("foo"));
 
   // DoesIndexExist() is case-sensitive.
-  EXPECT_FALSE(db().DoesIndexExist("Foo_index"));
-  EXPECT_FALSE(db().DoesIndexExist("Foo_Index"));
-  EXPECT_FALSE(db().DoesIndexExist("FOO_INDEX"));
+  EXPECT_FALSE(db_.DoesIndexExist("Foo_index"));
+  EXPECT_FALSE(db_.DoesIndexExist("Foo_Index"));
+  EXPECT_FALSE(db_.DoesIndexExist("FOO_INDEX"));
 }
 
 TEST_P(SQLDatabaseTest, DoesViewExist) {
-  EXPECT_FALSE(db().DoesViewExist("voo"));
-  ASSERT_TRUE(db().Execute("CREATE VIEW voo (a) AS SELECT 1"));
-  EXPECT_FALSE(db().DoesIndexExist("voo"));
-  EXPECT_FALSE(db().DoesTableExist("voo"));
-  EXPECT_TRUE(db().DoesViewExist("voo"));
+  EXPECT_FALSE(db_.DoesViewExist("voo"));
+  ASSERT_TRUE(db_.Execute("CREATE VIEW voo (a) AS SELECT 1"));
+  EXPECT_FALSE(db_.DoesIndexExist("voo"));
+  EXPECT_FALSE(db_.DoesTableExist("voo"));
+  EXPECT_TRUE(db_.DoesViewExist("voo"));
 
   // DoesTableExist() is case-sensitive.
-  EXPECT_FALSE(db().DoesViewExist("Voo"));
-  EXPECT_FALSE(db().DoesViewExist("VOO"));
+  EXPECT_FALSE(db_.DoesViewExist("Voo"));
+  EXPECT_FALSE(db_.DoesViewExist("VOO"));
 }
 
 TEST_P(SQLDatabaseTest, DoesColumnExist) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
 
-  EXPECT_FALSE(db().DoesColumnExist("foo", "bar"));
-  EXPECT_TRUE(db().DoesColumnExist("foo", "a"));
+  EXPECT_FALSE(db_.DoesColumnExist("foo", "bar"));
+  EXPECT_TRUE(db_.DoesColumnExist("foo", "a"));
 
-  ASSERT_FALSE(db().DoesTableExist("bar"));
-  EXPECT_FALSE(db().DoesColumnExist("bar", "b"));
+  ASSERT_FALSE(db_.DoesTableExist("bar"));
+  EXPECT_FALSE(db_.DoesColumnExist("bar", "b"));
 
   // SQLite resolves table/column names without case sensitivity.
-  EXPECT_TRUE(db().DoesColumnExist("FOO", "A"));
-  EXPECT_TRUE(db().DoesColumnExist("FOO", "a"));
-  EXPECT_TRUE(db().DoesColumnExist("foo", "A"));
+  EXPECT_TRUE(db_.DoesColumnExist("FOO", "A"));
+  EXPECT_TRUE(db_.DoesColumnExist("FOO", "a"));
+  EXPECT_TRUE(db_.DoesColumnExist("foo", "A"));
 }
 
 TEST_P(SQLDatabaseTest, GetLastInsertRowId) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
 
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (value) VALUES (12)"));
 
   // Last insert row ID should be valid.
-  int64_t row = db().GetLastInsertRowId();
+  int64_t row = db_.GetLastInsertRowId();
   EXPECT_LT(0, row);
 
   // It should be the primary key of the row we just inserted.
-  sql::Statement s(db().GetUniqueStatement("SELECT value FROM foo WHERE id=?"));
+  Statement s(db_.GetUniqueStatement("SELECT value FROM foo WHERE id=?"));
   s.BindInt64(0, row);
   ASSERT_TRUE(s.Step());
   EXPECT_EQ(12, s.ColumnInt(0));
 }
 
 TEST_P(SQLDatabaseTest, Rollback) {
-  ASSERT_TRUE(db().BeginTransaction());
-  ASSERT_TRUE(db().BeginTransaction());
-  EXPECT_EQ(2, db().transaction_nesting());
-  db().RollbackTransaction();
-  EXPECT_FALSE(db().CommitTransaction());
-  EXPECT_TRUE(db().BeginTransaction());
+  ASSERT_TRUE(db_.BeginTransaction());
+  ASSERT_TRUE(db_.BeginTransaction());
+  EXPECT_EQ(2, db_.transaction_nesting());
+  db_.RollbackTransaction();
+  EXPECT_FALSE(db_.CommitTransaction());
+  EXPECT_TRUE(db_.BeginTransaction());
 }
 
 // Test the scoped error expecter by attempting to insert a duplicate
 // value into an index.
 TEST_P(SQLDatabaseTest, ScopedErrorExpecter) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
 
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CONSTRAINT);
-    ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+    ASSERT_FALSE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 }
@@ -323,36 +361,36 @@
 // with ScopedErrorExpecter.
 TEST_P(SQLDatabaseTest, ScopedIgnoreUntracked) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_FALSE(db().DoesTableExist("bar"));
-  ASSERT_TRUE(db().DoesTableExist("foo"));
-  ASSERT_TRUE(db().DoesColumnExist("foo", "id"));
-  db().Close();
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_FALSE(db_.DoesTableExist("bar"));
+  ASSERT_TRUE(db_.DoesTableExist("foo"));
+  ASSERT_TRUE(db_.DoesColumnExist("foo", "id"));
+  db_.Close();
 
   // Corrupt the database so that nothing works, including PRAGMAs.
-  ASSERT_TRUE(CorruptSizeInHeaderOfDB());
+  ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path_));
 
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
-    ASSERT_TRUE(db().Open(db_path()));
-    ASSERT_FALSE(db().DoesTableExist("bar"));
-    ASSERT_FALSE(db().DoesTableExist("foo"));
-    ASSERT_FALSE(db().DoesColumnExist("foo", "id"));
+    ASSERT_TRUE(db_.Open(db_path_));
+    ASSERT_FALSE(db_.DoesTableExist("bar"));
+    ASSERT_FALSE(db_.DoesTableExist("foo"));
+    ASSERT_FALSE(db_.DoesColumnExist("foo", "id"));
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 }
 
 TEST_P(SQLDatabaseTest, ErrorCallback) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
 
   int error = SQLITE_OK;
   {
-    sql::ScopedErrorCallback sec(
-        &db(), base::BindRepeating(&sql::CaptureErrorCallback, &error));
-    EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+    ScopedErrorCallback sec(&db_,
+                            base::BindRepeating(&CaptureErrorCallback, &error));
+    EXPECT_FALSE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
 
     // Later versions of SQLite throw SQLITE_CONSTRAINT_UNIQUE.  The specific
     // sub-error isn't really important.
@@ -364,7 +402,7 @@
     error = SQLITE_OK;
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CONSTRAINT);
-    ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+    ASSERT_FALSE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
     ASSERT_TRUE(expecter.SawExpectedErrors());
     EXPECT_EQ(SQLITE_OK, error);
   }
@@ -380,34 +418,34 @@
   // live.
   {
     size_t count = 0;
-    sql::ScopedErrorCallback sec(
-        &db(), base::BindRepeating(&ErrorCallbackSetHelper, &db(), &count,
-                                   RefCounter(&count)));
+    ScopedErrorCallback sec(
+        &db_, base::BindRepeating(&ErrorCallbackSetHelper, &db_, &count,
+                                  RefCounter(&count)));
 
-    EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+    EXPECT_FALSE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
   }
 
   // Same test, but reset_error_callback() case.
   {
     size_t count = 0;
-    sql::ScopedErrorCallback sec(
-        &db(), base::BindRepeating(&ErrorCallbackResetHelper, &db(), &count,
-                                   RefCounter(&count)));
+    ScopedErrorCallback sec(
+        &db_, base::BindRepeating(&ErrorCallbackResetHelper, &db_, &count,
+                                  RefCounter(&count)));
 
-    EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
+    EXPECT_FALSE(db_.Execute("INSERT INTO foo (id) VALUES (12)"));
   }
 }
 
-// Test that sql::Database::Raze() results in a database without the
+// Test that Database::Raze() results in a database without the
 // tables from the original database.
 TEST_P(SQLDatabaseTest, Raze) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (value) VALUES (12)"));
 
   int pragma_auto_vacuum = 0;
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum"));
+    Statement s(db_.GetUniqueStatement("PRAGMA auto_vacuum"));
     ASSERT_TRUE(s.Step());
     pragma_auto_vacuum = s.ColumnInt(0);
     ASSERT_TRUE(pragma_auto_vacuum == 0 || pragma_auto_vacuum == 1);
@@ -417,13 +455,13 @@
   const int kExpectedPageCount = 2 + pragma_auto_vacuum;
 
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA page_count"));
+    Statement s(db_.GetUniqueStatement("PRAGMA page_count"));
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(kExpectedPageCount, s.ColumnInt(0));
   }
 
   {
-    sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master"));
+    Statement s(db_.GetUniqueStatement("SELECT * FROM sqlite_master"));
     ASSERT_TRUE(s.Step());
     EXPECT_EQ("table", s.ColumnString(0));
     EXPECT_EQ("foo", s.ColumnString(1));
@@ -433,18 +471,18 @@
     EXPECT_EQ(kCreateSql, s.ColumnString(4));
   }
 
-  ASSERT_TRUE(db().Raze());
+  ASSERT_TRUE(db_.Raze());
 
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA page_count"));
+    Statement s(db_.GetUniqueStatement("PRAGMA page_count"));
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(1, s.ColumnInt(0));
   }
 
-  ASSERT_EQ(0, SqliteMasterCount(&db()));
+  ASSERT_EQ(0, SqliteMasterCount(&db_));
 
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum"));
+    Statement s(db_.GetUniqueStatement("PRAGMA auto_vacuum"));
     ASSERT_TRUE(s.Step());
     // The new database has the same auto_vacuum as a fresh database.
     EXPECT_EQ(pragma_auto_vacuum, s.ColumnInt(0));
@@ -466,8 +504,8 @@
 
   const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII(
       base::NumberToString(initial_page_size));
-  sql::Database::Delete(db_path);
-  sql::Database db({.page_size = initial_page_size});
+  Database::Delete(db_path);
+  Database db({.page_size = initial_page_size});
   ASSERT_TRUE(db.Open(db_path));
   ASSERT_TRUE(db.Execute(kCreateSql));
   ASSERT_TRUE(db.Execute(kInsertSql1));
@@ -477,7 +515,7 @@
   db.Close();
 
   // Re-open the database while setting a new |options.page_size| in the object.
-  sql::Database razed_db({.page_size = final_page_size});
+  Database razed_db({.page_size = final_page_size});
   ASSERT_TRUE(razed_db.Open(db_path));
   // Raze will use the page size set in the connection object, which may not
   // match the file's page size.
@@ -495,29 +533,29 @@
   EXPECT_EQ("1", ExecuteWithResult(&razed_db, "PRAGMA page_count"));
 }
 
-// Verify that sql::Recovery maintains the page size, and the virtual table
+// Verify that Recovery maintains the page size, and the virtual table
 // works with page sizes other than SQLite's default.  Also verify the case
 // where the default page size has changed.
 TEST_P(SQLDatabaseTest, RazePageSize) {
   const std::string default_page_size =
-      ExecuteWithResult(&db(), "PRAGMA page_size");
+      ExecuteWithResult(&db_, "PRAGMA page_size");
 
   // Sync uses 32k pages.
   EXPECT_NO_FATAL_FAILURE(
-      TestPageSize(db_path(), 32768, "32768", 32768, "32768"));
+      TestPageSize(db_path_, 32768, "32768", 32768, "32768"));
 
   // Many clients use 4k pages.  This is the SQLite default after 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 4096, "4096", 4096, "4096"));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 4096, "4096", 4096, "4096"));
 
   // 1k is the default page size before 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 1024, "1024", 1024, "1024"));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 1024, "1024", 1024, "1024"));
 
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 2048, "2048", 4096, "4096"));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048", 4096, "4096"));
 
   // Databases with no page size specified should result in the default
   // page size.  2k has never been the default page size.
   ASSERT_NE("2048", default_page_size);
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 2048, "2048",
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
                                        DatabaseOptions::kDefaultPageSize,
                                        default_page_size));
 }
@@ -525,15 +563,15 @@
 // Test that Raze() results are seen in other connections.
 TEST_P(SQLDatabaseTest, RazeMultiple) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
 
-  sql::Database other_db(GetDBOptions());
-  ASSERT_TRUE(other_db.Open(db_path()));
+  Database other_db(GetDBOptions());
+  ASSERT_TRUE(other_db.Open(db_path_));
 
   // Check that the second connection sees the table.
   ASSERT_EQ(1, SqliteMasterCount(&other_db));
 
-  ASSERT_TRUE(db().Raze());
+  ASSERT_TRUE(db_.Raze());
 
   // The second connection sees the updated database.
   ASSERT_EQ(0, SqliteMasterCount(&other_db));
@@ -541,26 +579,26 @@
 
 TEST_P(SQLDatabaseTest, RazeLocked) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
 
   // Open a transaction and write some data in a second connection.
   // This will acquire a PENDING or EXCLUSIVE transaction, which will
   // cause the raze to fail.
-  sql::Database other_db(GetDBOptions());
-  ASSERT_TRUE(other_db.Open(db_path()));
+  Database other_db(GetDBOptions());
+  ASSERT_TRUE(other_db.Open(db_path_));
   ASSERT_TRUE(other_db.BeginTransaction());
   const char* kInsertSql = "INSERT INTO foo VALUES (1, 'data')";
   ASSERT_TRUE(other_db.Execute(kInsertSql));
 
-  ASSERT_FALSE(db().Raze());
+  ASSERT_FALSE(db_.Raze());
 
   // Works after COMMIT.
   ASSERT_TRUE(other_db.CommitTransaction());
-  ASSERT_TRUE(db().Raze());
+  ASSERT_TRUE(db_.Raze());
 
   // Re-create the database.
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kInsertSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
 
   // An unfinished read transaction in the other connection also
   // blocks raze.
@@ -568,13 +606,13 @@
   // write operations when using a WAL.
   if (!IsWALEnabled()) {
     const char* kQuery = "SELECT COUNT(*) FROM foo";
-    sql::Statement s(other_db.GetUniqueStatement(kQuery));
+    Statement s(other_db.GetUniqueStatement(kQuery));
     ASSERT_TRUE(s.Step());
-    ASSERT_FALSE(db().Raze());
+    ASSERT_FALSE(db_.Raze());
 
     // Completing the statement unlocks the database.
     ASSERT_FALSE(s.Step());
-    ASSERT_TRUE(db().Raze());
+    ASSERT_TRUE(db_.Raze());
   }
 }
 
@@ -582,26 +620,26 @@
 // this as an empty database.
 TEST_P(SQLDatabaseTest, RazeEmptyDB) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  db().Close();
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  db_.Close();
 
-  TruncateDatabase();
+  ASSERT_TRUE(TruncateDatabase());
 
-  ASSERT_TRUE(db().Open(db_path()));
-  ASSERT_TRUE(db().Raze());
-  EXPECT_EQ(0, SqliteMasterCount(&db()));
+  ASSERT_TRUE(db_.Open(db_path_));
+  ASSERT_TRUE(db_.Raze());
+  EXPECT_EQ(0, SqliteMasterCount(&db_));
 }
 
 // Verify that Raze() can handle a file of junk.
 // Need exclusive mode off here as there are some subtleties (by design) around
 // how the cache is used with it on which causes the test to fail.
 TEST_P(SQLDatabaseTest, RazeNOTADB) {
-  db().Close();
-  sql::Database::Delete(db_path());
-  ASSERT_FALSE(GetPathExists(db_path()));
+  db_.Close();
+  Database::Delete(db_path_);
+  ASSERT_FALSE(base::PathExists(db_path_));
 
-  WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE_AND_TRUNCATE);
-  ASSERT_TRUE(GetPathExists(db_path()));
+  ASSERT_TRUE(OverwriteDatabaseHeader(OverwriteType::kTruncate));
+  ASSERT_TRUE(base::PathExists(db_path_));
 
   // SQLite will successfully open the handle, but fail when running PRAGMA
   // statements that access the database.
@@ -609,25 +647,25 @@
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_NOTADB);
 
-    EXPECT_TRUE(db().Open(db_path()));
+    EXPECT_TRUE(db_.Open(db_path_));
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
-  EXPECT_TRUE(db().Raze());
-  db().Close();
+  EXPECT_TRUE(db_.Raze());
+  db_.Close();
 
   // Now empty, the open should open an empty database.
-  EXPECT_TRUE(db().Open(db_path()));
-  EXPECT_EQ(0, SqliteMasterCount(&db()));
+  EXPECT_TRUE(db_.Open(db_path_));
+  EXPECT_EQ(0, SqliteMasterCount(&db_));
 }
 
 // Verify that Raze() can handle a database overwritten with garbage.
 TEST_P(SQLDatabaseTest, RazeNOTADB2) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_EQ(1, SqliteMasterCount(&db()));
-  db().Close();
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_EQ(1, SqliteMasterCount(&db_));
+  db_.Close();
 
-  WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE);
+  ASSERT_TRUE(OverwriteDatabaseHeader(OverwriteType::kOverwrite));
 
   // SQLite will successfully open the handle, but will fail with
   // SQLITE_NOTADB on pragma statemenets which attempt to read the
@@ -635,15 +673,15 @@
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_NOTADB);
-    EXPECT_TRUE(db().Open(db_path()));
+    EXPECT_TRUE(db_.Open(db_path_));
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
-  EXPECT_TRUE(db().Raze());
-  db().Close();
+  EXPECT_TRUE(db_.Raze());
+  db_.Close();
 
   // Now empty, the open should succeed with an empty database.
-  EXPECT_TRUE(db().Open(db_path()));
-  EXPECT_EQ(0, SqliteMasterCount(&db()));
+  EXPECT_TRUE(db_.Open(db_path_));
+  EXPECT_EQ(0, SqliteMasterCount(&db_));
 }
 
 // Test that a callback from Open() can raze the database.  This is
@@ -652,34 +690,34 @@
 // callback does this during Open(), the open is retried and succeeds.
 TEST_P(SQLDatabaseTest, RazeCallbackReopen) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_EQ(1, SqliteMasterCount(&db()));
-  db().Close();
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_EQ(1, SqliteMasterCount(&db_));
+  db_.Close();
 
   // Corrupt the database so that nothing works, including PRAGMAs.
-  ASSERT_TRUE(CorruptSizeInHeaderOfDB());
+  ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path_));
 
   // Open() will succeed, even though the PRAGMA calls within will
   // fail with SQLITE_CORRUPT, as will this PRAGMA.
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
-    ASSERT_TRUE(db().Open(db_path()));
-    ASSERT_FALSE(db().Execute("PRAGMA auto_vacuum"));
-    db().Close();
+    ASSERT_TRUE(db_.Open(db_path_));
+    ASSERT_FALSE(db_.Execute("PRAGMA auto_vacuum"));
+    db_.Close();
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 
-  db().set_error_callback(
-      base::BindRepeating(&RazeErrorCallback, &db(), SQLITE_CORRUPT));
+  db_.set_error_callback(
+      base::BindRepeating(&RazeErrorCallback, &db_, SQLITE_CORRUPT));
 
   // When the PRAGMA calls in Open() raise SQLITE_CORRUPT, the error
   // callback will call RazeAndClose().  Open() will then fail and be
   // retried.  The second Open() on the empty database will succeed
   // cleanly.
-  ASSERT_TRUE(db().Open(db_path()));
-  ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum"));
-  EXPECT_EQ(0, SqliteMasterCount(&db()));
+  ASSERT_TRUE(db_.Open(db_path_));
+  ASSERT_TRUE(db_.Execute("PRAGMA auto_vacuum"));
+  EXPECT_EQ(0, SqliteMasterCount(&db_));
 }
 
 // Basic test of RazeAndClose() operation.
@@ -689,24 +727,24 @@
 
   // Test that RazeAndClose() closes the database, and that the
   // database is empty when re-opened.
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kPopulateSql));
-  ASSERT_TRUE(db().RazeAndClose());
-  ASSERT_FALSE(db().is_open());
-  db().Close();
-  ASSERT_TRUE(db().Open(db_path()));
-  ASSERT_EQ(0, SqliteMasterCount(&db()));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kPopulateSql));
+  ASSERT_TRUE(db_.RazeAndClose());
+  ASSERT_FALSE(db_.is_open());
+  db_.Close();
+  ASSERT_TRUE(db_.Open(db_path_));
+  ASSERT_EQ(0, SqliteMasterCount(&db_));
 
   // Test that RazeAndClose() can break transactions.
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kPopulateSql));
-  ASSERT_TRUE(db().BeginTransaction());
-  ASSERT_TRUE(db().RazeAndClose());
-  ASSERT_FALSE(db().is_open());
-  ASSERT_FALSE(db().CommitTransaction());
-  db().Close();
-  ASSERT_TRUE(db().Open(db_path()));
-  ASSERT_EQ(0, SqliteMasterCount(&db()));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kPopulateSql));
+  ASSERT_TRUE(db_.BeginTransaction());
+  ASSERT_TRUE(db_.RazeAndClose());
+  ASSERT_FALSE(db_.is_open());
+  ASSERT_FALSE(db_.CommitTransaction());
+  db_.Close();
+  ASSERT_TRUE(db_.Open(db_path_));
+  ASSERT_EQ(0, SqliteMasterCount(&db_));
 }
 
 // Test that various operations fail without crashing after
@@ -716,53 +754,53 @@
   const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)";
   const char* kSimpleSql = "SELECT 1";
 
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kPopulateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kPopulateSql));
 
   // Test baseline expectations.
-  db().Preload();
-  ASSERT_TRUE(db().DoesTableExist("foo"));
-  ASSERT_TRUE(db().IsSQLValid(kSimpleSql));
-  ASSERT_EQ(SQLITE_OK, db().ExecuteAndReturnErrorCode(kSimpleSql));
-  ASSERT_TRUE(db().Execute(kSimpleSql));
-  ASSERT_TRUE(db().is_open());
+  db_.Preload();
+  ASSERT_TRUE(db_.DoesTableExist("foo"));
+  ASSERT_TRUE(db_.IsSQLValid(kSimpleSql));
+  ASSERT_EQ(SQLITE_OK, db_.ExecuteAndReturnErrorCode(kSimpleSql));
+  ASSERT_TRUE(db_.Execute(kSimpleSql));
+  ASSERT_TRUE(db_.is_open());
   {
-    sql::Statement s(db().GetUniqueStatement(kSimpleSql));
+    Statement s(db_.GetUniqueStatement(kSimpleSql));
     ASSERT_TRUE(s.Step());
   }
   {
-    sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+    Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
     ASSERT_TRUE(s.Step());
   }
-  ASSERT_TRUE(db().BeginTransaction());
-  ASSERT_TRUE(db().CommitTransaction());
-  ASSERT_TRUE(db().BeginTransaction());
-  db().RollbackTransaction();
+  ASSERT_TRUE(db_.BeginTransaction());
+  ASSERT_TRUE(db_.CommitTransaction());
+  ASSERT_TRUE(db_.BeginTransaction());
+  db_.RollbackTransaction();
 
-  ASSERT_TRUE(db().RazeAndClose());
+  ASSERT_TRUE(db_.RazeAndClose());
 
   // At this point, they should all fail, but not crash.
-  db().Preload();
-  ASSERT_FALSE(db().DoesTableExist("foo"));
-  ASSERT_FALSE(db().IsSQLValid(kSimpleSql));
-  ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(kSimpleSql));
-  ASSERT_FALSE(db().Execute(kSimpleSql));
-  ASSERT_FALSE(db().is_open());
+  db_.Preload();
+  ASSERT_FALSE(db_.DoesTableExist("foo"));
+  ASSERT_FALSE(db_.IsSQLValid(kSimpleSql));
+  ASSERT_EQ(SQLITE_ERROR, db_.ExecuteAndReturnErrorCode(kSimpleSql));
+  ASSERT_FALSE(db_.Execute(kSimpleSql));
+  ASSERT_FALSE(db_.is_open());
   {
-    sql::Statement s(db().GetUniqueStatement(kSimpleSql));
+    Statement s(db_.GetUniqueStatement(kSimpleSql));
     ASSERT_FALSE(s.Step());
   }
   {
-    sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+    Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
     ASSERT_FALSE(s.Step());
   }
-  ASSERT_FALSE(db().BeginTransaction());
-  ASSERT_FALSE(db().CommitTransaction());
-  ASSERT_FALSE(db().BeginTransaction());
-  db().RollbackTransaction();
+  ASSERT_FALSE(db_.BeginTransaction());
+  ASSERT_FALSE(db_.CommitTransaction());
+  ASSERT_FALSE(db_.BeginTransaction());
+  db_.RollbackTransaction();
 
   // Close normally to reset the poisoned flag.
-  db().Close();
+  db_.Close();
 
 // DEATH tests not supported on Android, iOS, or Fuchsia.
 #if !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
@@ -770,7 +808,7 @@
   // usage by becoming fatal in debug mode.  Since DEATH tests are
   // expensive, just test one of them.
   if (DLOG_IS_ON(FATAL)) {
-    ASSERT_DEATH({ db().IsSQLValid(kSimpleSql); },
+    ASSERT_DEATH({ db_.IsSQLValid(kSimpleSql); },
                  "Illegal use of Database without a db");
   }
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
@@ -789,75 +827,75 @@
   // The empty database has 0 or 1 pages.  Raze() should leave it with exactly 1
   // page.  Not checking directly because auto_vacuum on Android adds a freelist
   // page.
-  ASSERT_TRUE(db().Raze());
+  ASSERT_TRUE(db_.Raze());
   int64_t expected_size;
-  ASSERT_TRUE(base::GetFileSize(db_path(), &expected_size));
+  ASSERT_TRUE(base::GetFileSize(db_path_, &expected_size));
   ASSERT_GT(expected_size, 0);
 
   // Cause the database to take a few pages.
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
   for (size_t i = 0; i < 24; ++i) {
     ASSERT_TRUE(
-        db().Execute("INSERT INTO foo (value) VALUES (randomblob(1024))"));
+        db_.Execute("INSERT INTO foo (value) VALUES (randomblob(1024))"));
   }
 
   // In WAL mode, writes don't reach the database file until a checkpoint
   // happens.
-  ASSERT_TRUE(db().CheckpointDatabase());
+  ASSERT_TRUE(db_.CheckpointDatabase());
 
   int64_t db_size;
-  ASSERT_TRUE(base::GetFileSize(db_path(), &db_size));
+  ASSERT_TRUE(base::GetFileSize(db_path_, &db_size));
   ASSERT_GT(db_size, expected_size);
 
   // Make a query covering most of the database file to make sure that the
   // blocks are actually mapped into memory.  Empirically, the truncate problem
   // doesn't seem to happen if no blocks are mapped.
   EXPECT_EQ("24576",
-            ExecuteWithResult(&db(), "SELECT SUM(LENGTH(value)) FROM foo"));
+            ExecuteWithResult(&db_, "SELECT SUM(LENGTH(value)) FROM foo"));
 
-  ASSERT_TRUE(db().Raze());
-  ASSERT_TRUE(base::GetFileSize(db_path(), &db_size));
+  ASSERT_TRUE(db_.Raze());
+  ASSERT_TRUE(base::GetFileSize(db_path_, &db_size));
   ASSERT_EQ(expected_size, db_size);
 }
 
 #if defined(OS_ANDROID)
 TEST_P(SQLDatabaseTest, SetTempDirForSQL) {
-  sql::MetaTable meta_table;
+  MetaTable meta_table;
   // Below call needs a temporary directory in sqlite3
   // On Android, it can pass only when the temporary directory is set.
   // Otherwise, sqlite3 doesn't find the correct directory to store
   // temporary files and will report the error 'unable to open
   // database file'.
-  ASSERT_TRUE(meta_table.Init(&db(), 4, 4));
+  ASSERT_TRUE(meta_table.Init(&db_, 4, 4));
 }
 #endif  // defined(OS_ANDROID)
 
 TEST_P(SQLDatabaseTest, Delete) {
-  EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
-  db().Close();
+  EXPECT_TRUE(db_.Execute("CREATE TABLE x (x)"));
+  db_.Close();
 
-  base::FilePath journal_path = sql::Database::JournalPath(db_path());
-  base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
+  base::FilePath journal_path = Database::JournalPath(db_path_);
+  base::FilePath wal_path = Database::WriteAheadLogPath(db_path_);
 
   // Should have both a main database file and a journal file if
   // journal_mode is TRUNCATE. There is no WAL file as it is deleted on Close.
-  ASSERT_TRUE(GetPathExists(db_path()));
+  ASSERT_TRUE(base::PathExists(db_path_));
   if (!IsWALEnabled()) {  // TRUNCATE mode
-    ASSERT_TRUE(GetPathExists(journal_path));
+    ASSERT_TRUE(base::PathExists(journal_path));
   }
 
-  sql::Database::Delete(db_path());
-  EXPECT_FALSE(GetPathExists(db_path()));
-  EXPECT_FALSE(GetPathExists(journal_path));
-  EXPECT_FALSE(GetPathExists(wal_path));
+  Database::Delete(db_path_);
+  EXPECT_FALSE(base::PathExists(db_path_));
+  EXPECT_FALSE(base::PathExists(journal_path));
+  EXPECT_FALSE(base::PathExists(wal_path));
 }
 
 #if defined(OS_POSIX)  // This test operates on POSIX file permissions.
 TEST_P(SQLDatabaseTest, PosixFilePermissions) {
-  db().Close();
-  sql::Database::Delete(db_path());
-  ASSERT_FALSE(GetPathExists(db_path()));
+  db_.Close();
+  Database::Delete(db_path_);
+  ASSERT_FALSE(base::PathExists(db_path_));
 
   // If the bots all had a restrictive umask setting such that databases are
   // always created with only the owner able to read them, then the code could
@@ -865,37 +903,37 @@
   // umask.
   ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH);
 
-  ASSERT_TRUE(db().Open(db_path()));
+  ASSERT_TRUE(db_.Open(db_path_));
 
   // Cause the journal file to be created. If the default journal_mode is
   // changed back to DELETE, this test will need to be updated.
-  EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
+  EXPECT_TRUE(db_.Execute("CREATE TABLE x (x)"));
 
   int mode;
-  ASSERT_TRUE(GetPathExists(db_path()));
-  EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
+  ASSERT_TRUE(base::PathExists(db_path_));
+  EXPECT_TRUE(base::GetPosixFilePermissions(db_path_, &mode));
   ASSERT_EQ(mode, 0600);
 
   if (IsWALEnabled()) {  // WAL mode
     // The WAL file is created lazily on first change.
-    ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+    ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
 
-    base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
-    ASSERT_TRUE(GetPathExists(wal_path));
+    base::FilePath wal_path = Database::WriteAheadLogPath(db_path_);
+    ASSERT_TRUE(base::PathExists(wal_path));
     EXPECT_TRUE(base::GetPosixFilePermissions(wal_path, &mode));
     ASSERT_EQ(mode, 0600);
 
     // The shm file doesn't exist in exclusive locking mode.
-    if (ExecuteWithResult(&db(), "PRAGMA locking_mode") == "normal") {
-      base::FilePath shm_path = sql::Database::SharedMemoryFilePath(db_path());
-      ASSERT_TRUE(GetPathExists(shm_path));
+    if (ExecuteWithResult(&db_, "PRAGMA locking_mode") == "normal") {
+      base::FilePath shm_path = Database::SharedMemoryFilePath(db_path_);
+      ASSERT_TRUE(base::PathExists(shm_path));
       EXPECT_TRUE(base::GetPosixFilePermissions(shm_path, &mode));
       ASSERT_EQ(mode, 0600);
     }
   } else {  // Truncate mode
-    base::FilePath journal_path = sql::Database::JournalPath(db_path());
+    base::FilePath journal_path = Database::JournalPath(db_path_);
     DLOG(ERROR) << "journal_path: " << journal_path;
-    ASSERT_TRUE(GetPathExists(journal_path));
+    ASSERT_TRUE(base::PathExists(journal_path));
     EXPECT_TRUE(base::GetPosixFilePermissions(journal_path, &mode));
     ASSERT_EQ(mode, 0600);
   }
@@ -904,31 +942,31 @@
 
 // Test that errors start happening once Poison() is called.
 TEST_P(SQLDatabaseTest, Poison) {
-  EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
+  EXPECT_TRUE(db_.Execute("CREATE TABLE x (x)"));
 
   // Before the Poison() call, things generally work.
-  EXPECT_TRUE(db().IsSQLValid("INSERT INTO x VALUES ('x')"));
-  EXPECT_TRUE(db().Execute("INSERT INTO x VALUES ('x')"));
+  EXPECT_TRUE(db_.IsSQLValid("INSERT INTO x VALUES ('x')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO x VALUES ('x')"));
   {
-    sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM x"));
+    Statement s(db_.GetUniqueStatement("SELECT COUNT(*) FROM x"));
     ASSERT_TRUE(s.is_valid());
     ASSERT_TRUE(s.Step());
   }
 
   // Get a statement which is valid before and will exist across Poison().
-  sql::Statement valid_statement(
-      db().GetUniqueStatement("SELECT COUNT(*) FROM sqlite_master"));
+  Statement valid_statement(
+      db_.GetUniqueStatement("SELECT COUNT(*) FROM sqlite_master"));
   ASSERT_TRUE(valid_statement.is_valid());
   ASSERT_TRUE(valid_statement.Step());
   valid_statement.Reset(true);
 
-  db().Poison();
+  db_.Poison();
 
   // After the Poison() call, things fail.
-  EXPECT_FALSE(db().IsSQLValid("INSERT INTO x VALUES ('x')"));
-  EXPECT_FALSE(db().Execute("INSERT INTO x VALUES ('x')"));
+  EXPECT_FALSE(db_.IsSQLValid("INSERT INTO x VALUES ('x')"));
+  EXPECT_FALSE(db_.Execute("INSERT INTO x VALUES ('x')"));
   {
-    sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM x"));
+    Statement s(db_.GetUniqueStatement("SELECT COUNT(*) FROM x"));
     ASSERT_FALSE(s.is_valid());
     ASSERT_FALSE(s.Step());
   }
@@ -939,77 +977,77 @@
 
   // Test that poisoning the database during a transaction works (with errors).
   // RazeErrorCallback() poisons the database, the extra COMMIT causes
-  // CommitTransaction() to throw an error while commiting.
-  db().set_error_callback(
-      base::BindRepeating(&RazeErrorCallback, &db(), SQLITE_ERROR));
-  db().Close();
-  ASSERT_TRUE(db().Open(db_path()));
-  EXPECT_TRUE(db().BeginTransaction());
-  EXPECT_TRUE(db().Execute("INSERT INTO x VALUES ('x')"));
-  EXPECT_TRUE(db().Execute("COMMIT"));
-  EXPECT_FALSE(db().CommitTransaction());
+  // CommitTransaction() to throw an error while committing.
+  db_.set_error_callback(
+      base::BindRepeating(&RazeErrorCallback, &db_, SQLITE_ERROR));
+  db_.Close();
+  ASSERT_TRUE(db_.Open(db_path_));
+  EXPECT_TRUE(db_.BeginTransaction());
+  EXPECT_TRUE(db_.Execute("INSERT INTO x VALUES ('x')"));
+  EXPECT_TRUE(db_.Execute("COMMIT"));
+  EXPECT_FALSE(db_.CommitTransaction());
 }
 
 TEST_P(SQLDatabaseTest, AttachDatabase) {
-  EXPECT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
 
   // Create a database to attach to.
   base::FilePath attach_path =
-      db_path().DirName().AppendASCII("SQLDatabaseAttach.db");
+      db_path_.DirName().AppendASCII("SQLDatabaseAttach.db");
   static const char kAttachmentPoint[] = "other";
   {
-    sql::Database other_db;
+    Database other_db;
     ASSERT_TRUE(other_db.Open(attach_path));
     EXPECT_TRUE(other_db.Execute("CREATE TABLE bar (a, b)"));
     EXPECT_TRUE(other_db.Execute("INSERT INTO bar VALUES ('hello', 'world')"));
   }
 
   // Cannot see the attached database, yet.
-  EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
+  EXPECT_FALSE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 
   EXPECT_TRUE(
-      DatabaseTestPeer::AttachDatabase(&db(), attach_path, kAttachmentPoint));
-  EXPECT_TRUE(db().IsSQLValid("SELECT count(*) from other.bar"));
+      DatabaseTestPeer::AttachDatabase(&db_, attach_path, kAttachmentPoint));
+  EXPECT_TRUE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 
   // Queries can touch both databases after the ATTACH.
-  EXPECT_TRUE(db().Execute("INSERT INTO foo SELECT a, b FROM other.bar"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO foo SELECT a, b FROM other.bar"));
   {
-    sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM foo"));
+    Statement s(db_.GetUniqueStatement("SELECT COUNT(*) FROM foo"));
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(1, s.ColumnInt(0));
   }
 
-  EXPECT_TRUE(DatabaseTestPeer::DetachDatabase(&db(), kAttachmentPoint));
-  EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
+  EXPECT_TRUE(DatabaseTestPeer::DetachDatabase(&db_, kAttachmentPoint));
+  EXPECT_FALSE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 }
 
 TEST_P(SQLDatabaseTest, AttachDatabaseWithOpenTransaction) {
-  EXPECT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
 
   // Create a database to attach to.
   base::FilePath attach_path =
-      db_path().DirName().AppendASCII("SQLDatabaseAttach.db");
+      db_path_.DirName().AppendASCII("SQLDatabaseAttach.db");
   static const char kAttachmentPoint[] = "other";
   {
-    sql::Database other_db;
+    Database other_db;
     ASSERT_TRUE(other_db.Open(attach_path));
     EXPECT_TRUE(other_db.Execute("CREATE TABLE bar (a, b)"));
     EXPECT_TRUE(other_db.Execute("INSERT INTO bar VALUES ('hello', 'world')"));
   }
 
   // Cannot see the attached database, yet.
-  EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
+  EXPECT_FALSE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 
   // Attach succeeds in a transaction.
-  EXPECT_TRUE(db().BeginTransaction());
+  EXPECT_TRUE(db_.BeginTransaction());
   EXPECT_TRUE(
-      DatabaseTestPeer::AttachDatabase(&db(), attach_path, kAttachmentPoint));
-  EXPECT_TRUE(db().IsSQLValid("SELECT count(*) from other.bar"));
+      DatabaseTestPeer::AttachDatabase(&db_, attach_path, kAttachmentPoint));
+  EXPECT_TRUE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 
   // Queries can touch both databases after the ATTACH.
-  EXPECT_TRUE(db().Execute("INSERT INTO foo SELECT a, b FROM other.bar"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO foo SELECT a, b FROM other.bar"));
   {
-    sql::Statement s(db().GetUniqueStatement("SELECT COUNT(*) FROM foo"));
+    Statement s(db_.GetUniqueStatement("SELECT COUNT(*) FROM foo"));
     ASSERT_TRUE(s.Step());
     EXPECT_EQ(1, s.ColumnInt(0));
   }
@@ -1018,30 +1056,30 @@
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(DatabaseTestPeer::DetachDatabase(&db(), kAttachmentPoint));
-    EXPECT_TRUE(db().IsSQLValid("SELECT count(*) from other.bar"));
+    EXPECT_FALSE(DatabaseTestPeer::DetachDatabase(&db_, kAttachmentPoint));
+    EXPECT_TRUE(db_.IsSQLValid("SELECT count(*) from other.bar"));
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 
   // Detach succeeds when the transaction is closed.
-  db().RollbackTransaction();
-  EXPECT_TRUE(DatabaseTestPeer::DetachDatabase(&db(), kAttachmentPoint));
-  EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
+  db_.RollbackTransaction();
+  EXPECT_TRUE(DatabaseTestPeer::DetachDatabase(&db_, kAttachmentPoint));
+  EXPECT_FALSE(db_.IsSQLValid("SELECT count(*) from other.bar"));
 }
 
 TEST_P(SQLDatabaseTest, Basic_QuickIntegrityCheck) {
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  EXPECT_TRUE(db().QuickIntegrityCheck());
-  db().Close();
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  EXPECT_TRUE(db_.QuickIntegrityCheck());
+  db_.Close();
 
-  ASSERT_TRUE(CorruptSizeInHeaderOfDB());
+  ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path_));
 
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
-    ASSERT_TRUE(db().Open(db_path()));
-    EXPECT_FALSE(db().QuickIntegrityCheck());
+    ASSERT_TRUE(db_.Open(db_path_));
+    EXPECT_FALSE(db_.QuickIntegrityCheck());
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 }
@@ -1051,19 +1089,19 @@
   std::vector<std::string> messages;
 
   const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  EXPECT_TRUE(db().FullIntegrityCheck(&messages));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  EXPECT_TRUE(db_.FullIntegrityCheck(&messages));
   EXPECT_EQ(1u, messages.size());
   EXPECT_EQ(kOk, messages[0]);
-  db().Close();
+  db_.Close();
 
-  ASSERT_TRUE(CorruptSizeInHeaderOfDB());
+  ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path_));
 
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
-    ASSERT_TRUE(db().Open(db_path()));
-    EXPECT_TRUE(db().FullIntegrityCheck(&messages));
+    ASSERT_TRUE(db_.Open(db_path_));
+    EXPECT_TRUE(db_.FullIntegrityCheck(&messages));
     EXPECT_LT(1u, messages.size());
     EXPECT_NE(kOk, messages[0]);
     ASSERT_TRUE(expecter.SawExpectedErrors());
@@ -1077,38 +1115,38 @@
   base::trace_event::MemoryDumpArgs args = {
       base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
   base::trace_event::ProcessMemoryDump pmd(args);
-  ASSERT_TRUE(db().memory_dump_provider_->OnMemoryDump(args, &pmd));
+  ASSERT_TRUE(db_.memory_dump_provider_->OnMemoryDump(args, &pmd));
   EXPECT_GE(pmd.allocator_dumps().size(), 1u);
 }
 
 // Test that the functions to collect diagnostic data run to completion, without
 // worrying too much about what they generate (since that will change).
 TEST_P(SQLDatabaseTest, CollectDiagnosticInfo) {
-  const std::string corruption_info = db().CollectCorruptionInfo();
+  const std::string corruption_info = db_.CollectCorruptionInfo();
   EXPECT_NE(std::string::npos, corruption_info.find("SQLITE_CORRUPT"));
   EXPECT_NE(std::string::npos, corruption_info.find("integrity_check"));
 
   // A statement to see in the results.
   const char* kSimpleSql = "SELECT 'mountain'";
-  Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+  Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
 
   // Error includes the statement.
-  const std::string readonly_info = db().CollectErrorInfo(SQLITE_READONLY, &s);
+  const std::string readonly_info = db_.CollectErrorInfo(SQLITE_READONLY, &s);
   EXPECT_NE(std::string::npos, readonly_info.find(kSimpleSql));
 
   // Some other error doesn't include the statment.
   // TODO(shess): This is weak.
-  const std::string full_info = db().CollectErrorInfo(SQLITE_FULL, nullptr);
+  const std::string full_info = db_.CollectErrorInfo(SQLITE_FULL, nullptr);
   EXPECT_EQ(std::string::npos, full_info.find(kSimpleSql));
 
   // A table to see in the SQLITE_ERROR results.
-  EXPECT_TRUE(db().Execute("CREATE TABLE volcano (x)"));
+  EXPECT_TRUE(db_.Execute("CREATE TABLE volcano (x)"));
 
   // Version info to see in the SQLITE_ERROR results.
-  sql::MetaTable meta_table;
-  ASSERT_TRUE(meta_table.Init(&db(), 4, 4));
+  MetaTable meta_table;
+  ASSERT_TRUE(meta_table.Init(&db_, 4, 4));
 
-  const std::string error_info = db().CollectErrorInfo(SQLITE_ERROR, &s);
+  const std::string error_info = db_.CollectErrorInfo(SQLITE_ERROR, &s);
   EXPECT_NE(std::string::npos, error_info.find(kSimpleSql));
   EXPECT_NE(std::string::npos, error_info.find("volcano"));
   EXPECT_NE(std::string::npos, error_info.find("version: 4"));
@@ -1118,7 +1156,7 @@
 // enabled by SQLite.
 TEST_P(SQLDatabaseTest, MmapInitiallyEnabled) {
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
+    Statement s(db_.GetUniqueStatement("PRAGMA mmap_size"));
     ASSERT_TRUE(s.Step())
         << "All supported SQLite versions should have mmap support";
 
@@ -1127,7 +1165,7 @@
     // returned.  If the VFS does not understand SQLITE_FCNTL_MMAP_SIZE (for
     // instance MojoVFS), -1 is returned.
     if (s.ColumnInt(0) <= 0) {
-      ASSERT_TRUE(db().Execute("PRAGMA mmap_size = 1048576"));
+      ASSERT_TRUE(db_.Execute("PRAGMA mmap_size = 1048576"));
       s.Reset(true);
       ASSERT_TRUE(s.Step());
       EXPECT_LE(s.ColumnInt(0), 0);
@@ -1135,24 +1173,24 @@
   }
 
   // Test that explicit disable prevents mmap'ed I/O.
-  db().Close();
-  sql::Database::Delete(db_path());
-  db().set_mmap_disabled();
-  ASSERT_TRUE(db().Open(db_path()));
-  EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA mmap_size"));
+  db_.Close();
+  Database::Delete(db_path_);
+  db_.set_mmap_disabled();
+  ASSERT_TRUE(db_.Open(db_path_));
+  EXPECT_EQ("0", ExecuteWithResult(&db_, "PRAGMA mmap_size"));
 }
 
 // Test whether a fresh database gets mmap enabled when using alternate status
 // storage.
 TEST_P(SQLDatabaseTest, MmapInitiallyEnabledAltStatus) {
   // Re-open fresh database with alt-status flag set.
-  db().Close();
-  sql::Database::Delete(db_path());
-  db().set_mmap_alt_status();
-  ASSERT_TRUE(db().Open(db_path()));
+  db_.Close();
+  Database::Delete(db_path_);
+  db_.set_mmap_alt_status();
+  ASSERT_TRUE(db_.Open(db_path_));
 
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
+    Statement s(db_.GetUniqueStatement("PRAGMA mmap_size"));
     ASSERT_TRUE(s.Step())
         << "All supported SQLite versions should have mmap support";
 
@@ -1161,7 +1199,7 @@
     // returned.  If the VFS does not understand SQLITE_FCNTL_MMAP_SIZE (for
     // instance MojoVFS), -1 is returned.
     if (s.ColumnInt(0) <= 0) {
-      ASSERT_TRUE(db().Execute("PRAGMA mmap_size = 1048576"));
+      ASSERT_TRUE(db_.Execute("PRAGMA mmap_size = 1048576"));
       s.Reset(true);
       ASSERT_TRUE(s.Step());
       EXPECT_LE(s.ColumnInt(0), 0);
@@ -1169,11 +1207,11 @@
   }
 
   // Test that explicit disable overrides set_mmap_alt_status().
-  db().Close();
-  sql::Database::Delete(db_path());
-  db().set_mmap_disabled();
-  ASSERT_TRUE(db().Open(db_path()));
-  EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA mmap_size"));
+  db_.Close();
+  Database::Delete(db_path_);
+  db_.set_mmap_disabled();
+  ASSERT_TRUE(db_.Open(db_path_));
+  EXPECT_EQ("0", ExecuteWithResult(&db_, "PRAGMA mmap_size"));
 }
 
 TEST_P(SQLDatabaseTest, GetAppropriateMmapSize) {
@@ -1182,40 +1220,40 @@
 
   // If there is no meta table (as for a fresh database), assume that everything
   // should be mapped, and the status of the meta table is not affected.
-  ASSERT_TRUE(!db().DoesTableExist("meta"));
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_TRUE(!db().DoesTableExist("meta"));
+  ASSERT_TRUE(!db_.DoesTableExist("meta"));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_TRUE(!db_.DoesTableExist("meta"));
 
   // When the meta table is first created, it sets up to map everything.
-  MetaTable().Init(&db(), 1, 1);
-  ASSERT_TRUE(db().DoesTableExist("meta"));
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_TRUE(MetaTable::GetMmapStatus(&db(), &mmap_status));
+  MetaTable().Init(&db_, 1, 1);
+  ASSERT_TRUE(db_.DoesTableExist("meta"));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_TRUE(MetaTable::GetMmapStatus(&db_, &mmap_status));
   ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status);
 
   // Preload with partial progress of one page.  Should map everything.
-  ASSERT_TRUE(db().Execute("REPLACE INTO meta VALUES ('mmap_status', 1)"));
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_TRUE(MetaTable::GetMmapStatus(&db(), &mmap_status));
+  ASSERT_TRUE(db_.Execute("REPLACE INTO meta VALUES ('mmap_status', 1)"));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_TRUE(MetaTable::GetMmapStatus(&db_, &mmap_status));
   ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status);
 
   // Failure status maps nothing.
-  ASSERT_TRUE(db().Execute("REPLACE INTO meta VALUES ('mmap_status', -2)"));
-  ASSERT_EQ(0UL, db().GetAppropriateMmapSize());
+  ASSERT_TRUE(db_.Execute("REPLACE INTO meta VALUES ('mmap_status', -2)"));
+  ASSERT_EQ(0UL, db_.GetAppropriateMmapSize());
 
   // Re-initializing the meta table does not re-create the key if the table
   // already exists.
-  ASSERT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'mmap_status'"));
-  MetaTable().Init(&db(), 1, 1);
+  ASSERT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'mmap_status'"));
+  MetaTable().Init(&db_, 1, 1);
   ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status);
-  ASSERT_TRUE(MetaTable::GetMmapStatus(&db(), &mmap_status));
+  ASSERT_TRUE(MetaTable::GetMmapStatus(&db_, &mmap_status));
   ASSERT_EQ(0, mmap_status);
 
   // With no key, map everything and create the key.
   // TODO(shess): This really should be "maps everything after validating it",
   // but that is more complicated to structure.
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_TRUE(MetaTable::GetMmapStatus(&db(), &mmap_status));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_TRUE(MetaTable::GetMmapStatus(&db_, &mmap_status));
   ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status);
 }
 
@@ -1223,57 +1261,57 @@
   const size_t kMmapAlot = 25 * 1024 * 1024;
 
   // At this point, Database still expects a future [meta] table.
-  ASSERT_FALSE(db().DoesTableExist("meta"));
-  ASSERT_FALSE(db().DoesViewExist("MmapStatus"));
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_FALSE(db().DoesTableExist("meta"));
-  ASSERT_FALSE(db().DoesViewExist("MmapStatus"));
+  ASSERT_FALSE(db_.DoesTableExist("meta"));
+  ASSERT_FALSE(db_.DoesViewExist("MmapStatus"));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_FALSE(db_.DoesTableExist("meta"));
+  ASSERT_FALSE(db_.DoesViewExist("MmapStatus"));
 
   // Using alt status, everything should be mapped, with state in the view.
-  db().set_mmap_alt_status();
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
-  ASSERT_FALSE(db().DoesTableExist("meta"));
-  ASSERT_TRUE(db().DoesViewExist("MmapStatus"));
+  db_.set_mmap_alt_status();
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_FALSE(db_.DoesTableExist("meta"));
+  ASSERT_TRUE(db_.DoesViewExist("MmapStatus"));
   EXPECT_EQ(base::NumberToString(MetaTable::kMmapSuccess),
-            ExecuteWithResult(&db(), "SELECT * FROM MmapStatus"));
+            ExecuteWithResult(&db_, "SELECT * FROM MmapStatus"));
 
   // Also maps everything when kMmapSuccess is already in the view.
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
 
   // Preload with partial progress of one page.  Should map everything.
-  ASSERT_TRUE(db().Execute("DROP VIEW MmapStatus"));
-  ASSERT_TRUE(db().Execute("CREATE VIEW MmapStatus (value) AS SELECT 1"));
-  ASSERT_GT(db().GetAppropriateMmapSize(), kMmapAlot);
+  ASSERT_TRUE(db_.Execute("DROP VIEW MmapStatus"));
+  ASSERT_TRUE(db_.Execute("CREATE VIEW MmapStatus (value) AS SELECT 1"));
+  ASSERT_GT(db_.GetAppropriateMmapSize(), kMmapAlot);
   EXPECT_EQ(base::NumberToString(MetaTable::kMmapSuccess),
-            ExecuteWithResult(&db(), "SELECT * FROM MmapStatus"));
+            ExecuteWithResult(&db_, "SELECT * FROM MmapStatus"));
 
   // Failure status leads to nothing being mapped.
-  ASSERT_TRUE(db().Execute("DROP VIEW MmapStatus"));
-  ASSERT_TRUE(db().Execute("CREATE VIEW MmapStatus (value) AS SELECT -2"));
-  ASSERT_EQ(0UL, db().GetAppropriateMmapSize());
+  ASSERT_TRUE(db_.Execute("DROP VIEW MmapStatus"));
+  ASSERT_TRUE(db_.Execute("CREATE VIEW MmapStatus (value) AS SELECT -2"));
+  ASSERT_EQ(0UL, db_.GetAppropriateMmapSize());
   EXPECT_EQ(base::NumberToString(MetaTable::kMmapFailure),
-            ExecuteWithResult(&db(), "SELECT * FROM MmapStatus"));
+            ExecuteWithResult(&db_, "SELECT * FROM MmapStatus"));
 }
 
 TEST_P(SQLDatabaseTest, GetMemoryUsage) {
   // Databases with mmap enabled may not follow the assumptions below.
-  db().Close();
-  db().set_mmap_disabled();
-  ASSERT_TRUE(db().Open(db_path()));
+  db_.Close();
+  db_.set_mmap_disabled();
+  ASSERT_TRUE(db_.Open(db_path_));
 
-  int initial_memory = db().GetMemoryUsage();
+  int initial_memory = db_.GetMemoryUsage();
   EXPECT_GT(initial_memory, 0)
       << "SQLite should always use some memory for a database";
 
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
 
-  int post_query_memory = db().GetMemoryUsage();
+  int post_query_memory = db_.GetMemoryUsage();
   EXPECT_GT(post_query_memory, initial_memory)
       << "Page cache usage should go up after executing queries";
 
-  db().TrimMemory();
-  int post_trim_memory = db().GetMemoryUsage();
+  db_.TrimMemory();
+  int post_trim_memory = db_.GetMemoryUsage();
   EXPECT_GT(post_query_memory, post_trim_memory)
       << "Page cache usage should go down after calling TrimMemory()";
 }
@@ -1290,66 +1328,66 @@
 };
 
 TEST_P(SQLDatabaseTestExclusiveMode, LockingModeExclusive) {
-  EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA locking_mode"), "exclusive");
+  EXPECT_EQ(ExecuteWithResult(&db_, "PRAGMA locking_mode"), "exclusive");
 }
 
 TEST_P(SQLDatabaseTest, LockingModeNormal) {
-  EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA locking_mode"), "normal");
+  EXPECT_EQ(ExecuteWithResult(&db_, "PRAGMA locking_mode"), "normal");
 }
 
 TEST_P(SQLDatabaseTest, OpenedInCorrectMode) {
   std::string expected_mode = IsWALEnabled() ? "wal" : "truncate";
-  EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), expected_mode);
+  EXPECT_EQ(ExecuteWithResult(&db_, "PRAGMA journal_mode"), expected_mode);
 }
 
 TEST_P(SQLDatabaseTest, CheckpointDatabase) {
   if (!IsWALEnabled())
     return;
 
-  base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
+  base::FilePath wal_path = Database::WriteAheadLogPath(db_path_);
 
   int64_t wal_size = 0;
   // WAL file initially empty.
-  EXPECT_TRUE(GetPathExists(wal_path));
+  EXPECT_TRUE(base::PathExists(wal_path));
   base::GetFileSize(wal_path, &wal_size);
   EXPECT_EQ(wal_size, 0);
 
   ASSERT_TRUE(
-      db().Execute("CREATE TABLE foo (id INTEGER UNIQUE, value INTEGER)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (1, 1)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo VALUES (2, 2)"));
+      db_.Execute("CREATE TABLE foo (id INTEGER UNIQUE, value INTEGER)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo VALUES (1, 1)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo VALUES (2, 2)"));
 
   // Writes reach WAL file but not db file.
   base::GetFileSize(wal_path, &wal_size);
   EXPECT_GT(wal_size, 0);
 
   int64_t db_size = 0;
-  base::GetFileSize(db_path(), &db_size);
-  EXPECT_EQ(db_size, db().page_size());
+  base::GetFileSize(db_path_, &db_size);
+  EXPECT_EQ(db_size, db_.page_size());
 
   // Checkpoint database to immediately propagate writes to DB file.
-  EXPECT_TRUE(db().CheckpointDatabase());
+  EXPECT_TRUE(db_.CheckpointDatabase());
 
-  base::GetFileSize(db_path(), &db_size);
-  EXPECT_GT(db_size, db().page_size());
-  EXPECT_EQ(ExecuteWithResult(&db(), "SELECT value FROM foo where id=1"), "1");
-  EXPECT_EQ(ExecuteWithResult(&db(), "SELECT value FROM foo where id=2"), "2");
+  base::GetFileSize(db_path_, &db_size);
+  EXPECT_GT(db_size, db_.page_size());
+  EXPECT_EQ(ExecuteWithResult(&db_, "SELECT value FROM foo where id=1"), "1");
+  EXPECT_EQ(ExecuteWithResult(&db_, "SELECT value FROM foo where id=2"), "2");
 }
 
 TEST_P(SQLDatabaseTest, CorruptSizeInHeaderTest) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (x)"));
-  ASSERT_TRUE(db().Execute("CREATE TABLE bar (x)"));
-  db().Close();
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (x)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE bar (x)"));
+  db_.Close();
 
-  ASSERT_TRUE(CorruptSizeInHeaderOfDB());
+  ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path_));
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
-    ASSERT_TRUE(db().Open(db_path()));
-    EXPECT_FALSE(db().Execute("INSERT INTO foo values (1)"));
-    EXPECT_FALSE(db().DoesTableExist("foo"));
-    EXPECT_FALSE(db().DoesTableExist("bar"));
-    EXPECT_FALSE(db().Execute("SELECT * FROM foo"));
+    ASSERT_TRUE(db_.Open(db_path_));
+    EXPECT_FALSE(db_.Execute("INSERT INTO foo values (1)"));
+    EXPECT_FALSE(db_.DoesTableExist("foo"));
+    EXPECT_FALSE(db_.DoesTableExist("bar"));
+    EXPECT_FALSE(db_.Execute("SELECT * FROM foo"));
     EXPECT_TRUE(expecter.SawExpectedErrors());
   }
 }
@@ -1361,8 +1399,8 @@
 // DEATH tests not supported on Android, iOS, or Fuchsia.
 #if !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
   if (DLOG_IS_ON(FATAL)) {
-    db().set_error_callback(base::BindRepeating(&IgnoreErrorCallback));
-    ASSERT_DEATH({ db().GetUniqueStatement("SELECT x"); },
+    db_.set_error_callback(base::BindRepeating(&IgnoreErrorCallback));
+    ASSERT_DEATH({ db_.GetUniqueStatement("SELECT x"); },
                  "SQL compile error no such column: x");
   }
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
diff --git a/sql/meta_table_unittest.cc b/sql/meta_table_unittest.cc
index 928c0ed0..e91380b 100644
--- a/sql/meta_table_unittest.cc
+++ b/sql/meta_table_unittest.cc
@@ -10,22 +10,36 @@
 #include "base/files/scoped_temp_dir.h"
 #include "sql/database.h"
 #include "sql/statement.h"
-#include "sql/test/sql_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace sql {
+
 namespace {
 
-using SQLMetaTableTest = sql::SQLTestBase;
+class SQLMetaTableTest : public testing::Test {
+ public:
+  ~SQLMetaTableTest() override = default;
 
-TEST_F(SQLMetaTableTest, DoesTableExist) {
-  EXPECT_FALSE(sql::MetaTable::DoesTableExist(&db()));
-
-  {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(
+        db_.Open(temp_dir_.GetPath().AppendASCII("meta_table_test.sqlite")));
   }
 
-  EXPECT_TRUE(sql::MetaTable::DoesTableExist(&db()));
+ protected:
+  base::ScopedTempDir temp_dir_;
+  Database db_;
+};
+
+TEST_F(SQLMetaTableTest, DoesTableExist) {
+  EXPECT_FALSE(MetaTable::DoesTableExist(&db_));
+
+  {
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
+  }
+
+  EXPECT_TRUE(MetaTable::DoesTableExist(&db_));
 }
 
 TEST_F(SQLMetaTableTest, RazeIfDeprecated) {
@@ -34,45 +48,45 @@
 
   // Setup a current database.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kVersion, kVersion));
-    EXPECT_TRUE(db().Execute("CREATE TABLE t(c)"));
-    EXPECT_TRUE(db().DoesTableExist("t"));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kVersion, kVersion));
+    EXPECT_TRUE(db_.Execute("CREATE TABLE t(c)"));
+    EXPECT_TRUE(db_.DoesTableExist("t"));
   }
 
   // Table should should still exist if the database version is new enough.
-  sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion);
-  EXPECT_TRUE(db().DoesTableExist("t"));
+  MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersion);
+  EXPECT_TRUE(db_.DoesTableExist("t"));
 
   // TODO(shess): It may make sense to Raze() if meta isn't present or
   // version isn't present.  See meta_table.h TODO on RazeIfDeprecated().
 
   // Table should still exist if the version is not available.
-  EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'"));
+  EXPECT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'version'"));
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kVersion, kVersion));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kVersion, kVersion));
     EXPECT_EQ(0, meta_table.GetVersionNumber());
   }
-  sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion);
-  EXPECT_TRUE(db().DoesTableExist("t"));
+  MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersion);
+  EXPECT_TRUE(db_.DoesTableExist("t"));
 
   // Table should still exist if meta table is missing.
-  EXPECT_TRUE(db().Execute("DROP TABLE meta"));
-  sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion);
-  EXPECT_TRUE(db().DoesTableExist("t"));
+  EXPECT_TRUE(db_.Execute("DROP TABLE meta"));
+  MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersion);
+  EXPECT_TRUE(db_.DoesTableExist("t"));
 
   // Setup meta with deprecated version.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kDeprecatedVersion, kDeprecatedVersion));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kDeprecatedVersion, kDeprecatedVersion));
   }
 
   // Deprecation check should remove the table.
-  EXPECT_TRUE(db().DoesTableExist("t"));
-  sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion);
-  EXPECT_FALSE(sql::MetaTable::DoesTableExist(&db()));
-  EXPECT_FALSE(db().DoesTableExist("t"));
+  EXPECT_TRUE(db_.DoesTableExist("t"));
+  MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersion);
+  EXPECT_FALSE(MetaTable::DoesTableExist(&db_));
+  EXPECT_FALSE(db_.DoesTableExist("t"));
 }
 
 TEST_F(SQLMetaTableTest, VersionNumber) {
@@ -87,16 +101,16 @@
 
   // First Init() sets the version info as expected.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kVersionFirst, kCompatVersionFirst));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kVersionFirst, kCompatVersionFirst));
     EXPECT_EQ(kVersionFirst, meta_table.GetVersionNumber());
     EXPECT_EQ(kCompatVersionFirst, meta_table.GetCompatibleVersionNumber());
   }
 
   // Second Init() does not change the version info.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kVersionSecond, kCompatVersionSecond));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kVersionSecond, kCompatVersionSecond));
     EXPECT_EQ(kVersionFirst, meta_table.GetVersionNumber());
     EXPECT_EQ(kCompatVersionFirst, meta_table.GetCompatibleVersionNumber());
 
@@ -106,8 +120,8 @@
 
   // Version info from Set*() calls is seen.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), kVersionThird, kCompatVersionThird));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, kVersionThird, kCompatVersionThird));
     EXPECT_EQ(kVersionSecond, meta_table.GetVersionNumber());
     EXPECT_EQ(kCompatVersionSecond, meta_table.GetCompatibleVersionNumber());
   }
@@ -120,8 +134,8 @@
 
   // Initially, the value isn't there until set.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     std::string value;
     EXPECT_FALSE(meta_table.GetValue(kKey, &value));
@@ -133,8 +147,8 @@
 
   // Value is persistent across different instances.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     std::string value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -145,8 +159,8 @@
 
   // Existing value was successfully changed.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     std::string value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -161,8 +175,8 @@
 
   // Initially, the value isn't there until set.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int value;
     EXPECT_FALSE(meta_table.GetValue(kKey, &value));
@@ -174,8 +188,8 @@
 
   // Value is persistent across different instances.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -186,8 +200,8 @@
 
   // Existing value was successfully changed.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -202,8 +216,8 @@
 
   // Initially, the value isn't there until set.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int64_t value;
     EXPECT_FALSE(meta_table.GetValue(kKey, &value));
@@ -215,8 +229,8 @@
 
   // Value is persistent across different instances.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int64_t value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -227,8 +241,8 @@
 
   // Existing value was successfully changed.
   {
-    sql::MetaTable meta_table;
-    EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+    MetaTable meta_table;
+    EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
     int64_t value;
     EXPECT_TRUE(meta_table.GetValue(kKey, &value));
@@ -240,8 +254,8 @@
   static const char kKey[] = "String Key";
   const std::string kValue("String Value");
 
-  sql::MetaTable meta_table;
-  EXPECT_TRUE(meta_table.Init(&db(), 1, 1));
+  MetaTable meta_table;
+  EXPECT_TRUE(meta_table.Init(&db_, 1, 1));
 
   // Value isn't present.
   std::string value;
@@ -258,3 +272,5 @@
 }
 
 }  // namespace
+
+}  // namespace sql
diff --git a/sql/recover_module/module_unittest.cc b/sql/recover_module/module_unittest.cc
index a711756..2e0db47 100644
--- a/sql/recover_module/module_unittest.cc
+++ b/sql/recover_module/module_unittest.cc
@@ -7,12 +7,12 @@
 #include <tuple>
 #include <vector>
 
+#include "base/files/scoped_temp_dir.h"
 #include "base/strings/stringprintf.h"
 #include "sql/database.h"
 #include "sql/statement.h"
 #include "sql/test/database_test_peer.h"
 #include "sql/test/scoped_error_expecter.h"
-#include "sql/test/sql_test_base.h"
 #include "sql/test/test_helpers.h"
 #include "sql/transaction.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -21,123 +21,131 @@
 namespace sql {
 namespace recover {
 
-class RecoverModuleTest : public sql::SQLTestBase {
+class RecoverModuleTest : public testing::Test {
  public:
+  ~RecoverModuleTest() override = default;
+
   void SetUp() override {
-    SQLTestBase::SetUp();
-    ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db()));
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(
+        db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite")));
+    ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_));
   }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  sql::Database db_;
 };
 
 TEST_F(RecoverModuleTest, CreateVtable) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   EXPECT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                   "USING recover(backing, t TEXT)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                  "USING recover(backing, t TEXT)"));
 }
 TEST_F(RecoverModuleTest, CreateVtableWithDatabaseSpecifier) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   EXPECT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                   "USING recover(main.backing, t TEXT)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                  "USING recover(main.backing, t TEXT)"));
 }
 TEST_F(RecoverModuleTest, CreateVtableOnSqliteMaster) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   EXPECT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
-                   "sqlite_master, type TEXT, name TEXT, tbl_name TEXT, "
-                   "rootpage INTEGER, sql TEXT)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
+                  "sqlite_master, type TEXT, name TEXT, tbl_name TEXT, "
+                  "rootpage INTEGER, sql TEXT)"));
 }
 
 TEST_F(RecoverModuleTest, CreateVtableFailsOnNonTempTable) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
-    EXPECT_FALSE(db().Execute(
+    EXPECT_FALSE(db_.Execute(
         "CREATE VIRTUAL TABLE recover_backing USING recover(backing, t TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingTable) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_CORRUPT);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_missing "
-                     "USING recover(missing, t TEXT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_missing "
+                    "USING recover(missing, t TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, DISABLED_CreateVtableFailsOnMissingDatabase) {
   // TODO(pwnall): Enable test after removing incorrect DLOG(FATAL) from
   //               sql::Statement::Execute().
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_ERROR);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(db.backing, t TEXT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(db.backing, t TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, CreateVtableFailsOnTableWithInvalidQualifier) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_CORRUPT);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing invalid, t TEXT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing invalid, t TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, DISABLED_CreateVtableFailsOnMissingTableName) {
   // TODO(pwnall): Enable test after removing incorrect DLOG(FATAL) from
   //               sql::Statement::Execute().
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_ERROR);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(main., t TEXT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(main., t TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingSchemaSpec) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingDbName) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(.backing)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(.backing)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 
 TEST_F(RecoverModuleTest, ColumnTypeMappingAny) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   EXPECT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                   "USING recover(backing, t ANY)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                  "USING recover(backing, t ANY)"));
 
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "t");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t");
   EXPECT_EQ("(nullptr)", column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_FALSE(column_info.has_non_null_constraint);
@@ -145,13 +153,13 @@
   EXPECT_FALSE(column_info.is_auto_incremented);
 }
 TEST_F(RecoverModuleTest, ColumnTypeMappingAnyNotNull) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   EXPECT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                   "USING recover(backing, t ANY NOT NULL)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                  "USING recover(backing, t ANY NOT NULL)"));
 
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "t");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t");
   EXPECT_EQ("(nullptr)", column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_TRUE(column_info.has_non_null_constraint);
@@ -159,58 +167,58 @@
   EXPECT_FALSE(column_info.is_auto_incremented);
 }
 TEST_F(RecoverModuleTest, ColumnTypeMappingAnyStrict) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing, t ANY STRICT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing, t ANY STRICT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 
 TEST_F(RecoverModuleTest, ColumnTypeExtraKeyword) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing, t INTEGER SOMETHING)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing, t INTEGER SOMETHING)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, ColumnTypeNotNullExtraKeyword) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing, t INTEGER NOT NULL SOMETHING)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing, t INTEGER NOT NULL SOMETHING)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, ColumnTypeDoubleTypes) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                     "USING recover(backing, t INTEGER FLOAT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
+                    "USING recover(backing, t INTEGER FLOAT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
 TEST_F(RecoverModuleTest, ColumnTypeNotNullDoubleTypes) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE backing(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
   {
     sql::test::ScopedErrorExpecter error_expecter;
     error_expecter.ExpectError(SQLITE_MISUSE);
     EXPECT_FALSE(
-        db().Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
-                     "backing, t INTEGER NOT NULL TEXT)"));
+        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
+                    "backing, t INTEGER NOT NULL TEXT)"));
     EXPECT_TRUE(error_expecter.SawExpectedErrors());
   }
 }
@@ -220,30 +228,39 @@
       public ::testing::WithParamInterface<
           std::tuple<const char*, const char*, bool>> {
  public:
+  ~RecoverModuleColumnTypeMappingTest() override = default;
+
   void SetUp() override {
-    RecoverModuleTest::SetUp();
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(
+        db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite")));
+    ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_));
+
     std::string sql =
         base::StringPrintf("CREATE TABLE backing(data %s)", SchemaType());
-    ASSERT_TRUE(db().Execute(sql.c_str()));
+    ASSERT_TRUE(db_.Execute(sql.c_str()));
   }
 
- protected:
   void CreateRecoveryTable(const char* suffix) {
     std::string sql = base::StringPrintf(
         "CREATE VIRTUAL TABLE temp.recover_backing "
         "USING recover(backing, data %s%s)",
         SchemaType(), suffix);
-    ASSERT_TRUE(db().Execute(sql.c_str()));
+    ASSERT_TRUE(db_.Execute(sql.c_str()));
   }
 
   const char* SchemaType() const { return std::get<0>(GetParam()); }
   const char* ExpectedType() const { return std::get<1>(GetParam()); }
   bool IsAlwaysNonNull() const { return std::get<2>(GetParam()); }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  sql::Database db_;
 };
 TEST_P(RecoverModuleColumnTypeMappingTest, Unqualified) {
   CreateRecoveryTable("");
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "data");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
   EXPECT_EQ(ExpectedType(), column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint);
@@ -253,7 +270,7 @@
 TEST_P(RecoverModuleColumnTypeMappingTest, NotNull) {
   CreateRecoveryTable(" NOT NULL");
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "data");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
   EXPECT_EQ(ExpectedType(), column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_TRUE(column_info.has_non_null_constraint);
@@ -263,7 +280,7 @@
 TEST_P(RecoverModuleColumnTypeMappingTest, Strict) {
   CreateRecoveryTable(" STRICT");
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "data");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
   EXPECT_EQ(ExpectedType(), column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint);
@@ -273,7 +290,7 @@
 TEST_P(RecoverModuleColumnTypeMappingTest, StrictNotNull) {
   CreateRecoveryTable(" STRICT NOT NULL");
   sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db(), "temp", "recover_backing", "data");
+      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
   EXPECT_EQ(ExpectedType(), column_info.data_type);
   EXPECT_EQ("BINARY", column_info.collation_sequence);
   EXPECT_TRUE(column_info.has_non_null_constraint);
@@ -305,12 +322,12 @@
 }  // namespace
 
 TEST_F(RecoverModuleTest, ReadFromAlteredTableNullDefaults) {
-  GenerateAlteredTable(&db());
+  GenerateAlteredTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_altered "
-                   "USING recover(altered, t TEXT, i INTEGER)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered "
+                  "USING recover(altered, t TEXT, i INTEGER)"));
 
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT t, i FROM recover_altered ORDER BY rowid"));
   ASSERT_TRUE(statement.Step());
   EXPECT_EQ("a", statement.ColumnString(0));
@@ -329,12 +346,12 @@
 }
 
 TEST_F(RecoverModuleTest, ReadFromAlteredTableSkipsNulls) {
-  GenerateAlteredTable(&db());
+  GenerateAlteredTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_altered "
-                   "USING recover(altered, t TEXT, i INTEGER NOT NULL)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered "
+                  "USING recover(altered, t TEXT, i INTEGER NOT NULL)"));
 
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT t, i FROM recover_altered ORDER BY rowid"));
   ASSERT_TRUE(statement.Step());
   EXPECT_EQ("d", statement.ColumnString(0));
@@ -366,13 +383,13 @@
 }  // namespace
 
 TEST_F(RecoverModuleTest, LeafNodes) {
-  GenerateSizedTable(&db(), 10, "Leaf-node-generating line ");
+  GenerateSizedTable(&db_, 10, "Leaf-node-generating line ");
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                   "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
+                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
 
   sql::Statement statement(
-      db().GetUniqueStatement("SELECT t, i FROM recover_sized ORDER BY rowid"));
+      db_.GetUniqueStatement("SELECT t, i FROM recover_sized ORDER BY rowid"));
   for (int i = 0; i < 10; ++i) {
     ASSERT_TRUE(statement.Step());
     EXPECT_EQ(base::StringPrintf("Leaf-node-generating line %d", i),
@@ -383,22 +400,22 @@
 }
 
 TEST_F(RecoverModuleTest, EmptyTable) {
-  GenerateSizedTable(&db(), 0, "");
+  GenerateSizedTable(&db_, 0, "");
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                   "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
+                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
   EXPECT_FALSE(statement.Step());
 }
 
 TEST_F(RecoverModuleTest, SingleLevelInteriorNodes) {
-  GenerateSizedTable(&db(), 100, "Interior-node-generating line ");
+  GenerateSizedTable(&db_, 100, "Interior-node-generating line ");
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                   "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
+                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
 
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
   for (int i = 0; i < 100; ++i) {
     ASSERT_TRUE(statement.Step());
@@ -411,12 +428,12 @@
 }
 
 TEST_F(RecoverModuleTest, MultiLevelInteriorNodes) {
-  GenerateSizedTable(&db(), 5000, "Interior-node-generating line ");
+  GenerateSizedTable(&db_, 5000, "Interior-node-generating line ");
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                   "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
+                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
 
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
   for (int i = 0; i < 5000; ++i) {
     ASSERT_TRUE(statement.Step());
@@ -444,11 +461,11 @@
 }  // namespace
 
 TEST_F(RecoverModuleTest, Any) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value ANY)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value ANY)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -482,11 +499,11 @@
 }
 
 TEST_F(RecoverModuleTest, Integers) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value INTEGER)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value INTEGER)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -503,11 +520,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullIntegers) {
-  GenerateTypesTable(&db());
-  ASSERT_TRUE(db().Execute(
+  GenerateTypesTable(&db_);
+  ASSERT_TRUE(db_.Execute(
       "CREATE VIRTUAL TABLE temp.recover_types "
       "USING recover(types, rowtype TEXT, value INTEGER NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -520,11 +537,11 @@
 }
 
 TEST_F(RecoverModuleTest, Floats) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value FLOAT)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value FLOAT)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -546,11 +563,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullFloats) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value FLOAT NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value FLOAT NOT NULL)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -568,11 +585,11 @@
 }
 
 TEST_F(RecoverModuleTest, FloatsStrict) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value FLOAT STRICT)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value FLOAT STRICT)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -589,11 +606,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullFloatsStrict) {
-  GenerateTypesTable(&db());
-  ASSERT_TRUE(db().Execute(
+  GenerateTypesTable(&db_);
+  ASSERT_TRUE(db_.Execute(
       "CREATE VIRTUAL TABLE temp.recover_types "
       "USING recover(types, rowtype TEXT, value FLOAT STRICT NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -606,11 +623,11 @@
 }
 
 TEST_F(RecoverModuleTest, Texts) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value TEXT)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value TEXT)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -634,11 +651,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullTexts) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value TEXT NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value TEXT NOT NULL)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -658,11 +675,11 @@
 }
 
 TEST_F(RecoverModuleTest, TextsStrict) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value TEXT STRICT)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value TEXT STRICT)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -679,11 +696,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullTextsStrict) {
-  GenerateTypesTable(&db());
-  ASSERT_TRUE(db().Execute(
+  GenerateTypesTable(&db_);
+  ASSERT_TRUE(db_.Execute(
       "CREATE VIRTUAL TABLE temp.recover_types "
       "USING recover(types, rowtype TEXT, value TEXT STRICT NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -696,11 +713,11 @@
 }
 
 TEST_F(RecoverModuleTest, Blobs) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value BLOB)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value BLOB)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -719,11 +736,11 @@
 }
 
 TEST_F(RecoverModuleTest, NonNullBlobs) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value BLOB NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value BLOB NOT NULL)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -738,11 +755,11 @@
 }
 
 TEST_F(RecoverModuleTest, AnyNonNull) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                   "USING recover(types, rowtype TEXT, value ANY NOT NULL)"));
-  sql::Statement statement(db().GetUniqueStatement(
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
+                  "USING recover(types, rowtype TEXT, value ANY NOT NULL)"));
+  sql::Statement statement(db_.GetUniqueStatement(
       "SELECT rowid, rowtype, value FROM recover_types"));
 
   ASSERT_TRUE(statement.Step());
@@ -772,20 +789,20 @@
 }
 
 TEST_F(RecoverModuleTest, RowidAlias) {
-  GenerateTypesTable(&db());
+  GenerateTypesTable(&db_);
 
   // The id column is an alias for rowid, and its values get serialized as NULL.
-  ASSERT_TRUE(db().Execute(
+  ASSERT_TRUE(db_.Execute(
       "CREATE TABLE types2(id INTEGER PRIMARY KEY, rowtype TEXT, value)"));
   ASSERT_TRUE(
-      db().Execute("INSERT INTO types2(id, rowtype, value) "
-                   "SELECT rowid, rowtype, value FROM types WHERE true"));
-  ASSERT_TRUE(db().Execute(
+      db_.Execute("INSERT INTO types2(id, rowtype, value) "
+                  "SELECT rowid, rowtype, value FROM types WHERE true"));
+  ASSERT_TRUE(db_.Execute(
       "CREATE VIRTUAL TABLE temp.recover_types2 "
       "USING recover(types2, id ROWID NOT NULL, rowtype TEXT, value ANY)"));
 
   sql::Statement statement(
-      db().GetUniqueStatement("SELECT id, rowid, rowtype, value FROM types2"));
+      db_.GetUniqueStatement("SELECT id, rowid, rowtype, value FROM types2"));
 
   ASSERT_TRUE(statement.Step());
   EXPECT_EQ(1, statement.ColumnInt(0));
@@ -819,7 +836,7 @@
 }
 
 TEST_F(RecoverModuleTest, IntegerEncodings) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE integers(value)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE integers(value)"));
 
   const std::vector<int64_t> values = {
       // Encoded directly in type info.
@@ -857,7 +874,7 @@
       -9223372036854775807,
   };
   sql::Statement insert(
-      db().GetUniqueStatement("INSERT INTO integers VALUES(?)"));
+      db_.GetUniqueStatement("INSERT INTO integers VALUES(?)"));
   for (int64_t value : values) {
     insert.BindInt64(0, value);
     ASSERT_TRUE(insert.Run());
@@ -865,10 +882,10 @@
   }
 
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_integers "
-                   "USING recover(integers, value INTEGER)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_integers "
+                  "USING recover(integers, value INTEGER)"));
   sql::Statement select(
-      db().GetUniqueStatement("SELECT rowid, value FROM recover_integers"));
+      db_.GetUniqueStatement("SELECT rowid, value FROM recover_integers"));
   for (size_t i = 0; i < values.size(); ++i) {
     ASSERT_TRUE(select.Step()) << "Was attemping to read " << values[i];
     EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
@@ -967,30 +984,30 @@
       -0x8000000000000000,
   };
 
-  ASSERT_TRUE(db().Execute("CREATE TABLE varints(value INTEGER PRIMARY KEY)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE varints(value INTEGER PRIMARY KEY)"));
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_varints "
-                   "USING recover(varints, value ROWID)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_varints "
+                  "USING recover(varints, value ROWID)"));
 
   for (int64_t value : values) {
     sql::Statement insert(
-        db().GetUniqueStatement("INSERT INTO varints VALUES(?)"));
+        db_.GetUniqueStatement("INSERT INTO varints VALUES(?)"));
     insert.BindInt64(0, value);
     ASSERT_TRUE(insert.Run());
 
     sql::Statement select(
-        db().GetUniqueStatement("SELECT rowid, value FROM recover_varints"));
+        db_.GetUniqueStatement("SELECT rowid, value FROM recover_varints"));
     ASSERT_TRUE(select.Step()) << "Was attemping to read " << value;
     EXPECT_EQ(value, select.ColumnInt64(0));
     EXPECT_EQ(value, select.ColumnInt64(1));
     EXPECT_FALSE(select.Step());
 
-    ASSERT_TRUE(db().Execute("DELETE FROM varints"));
+    ASSERT_TRUE(db_.Execute("DELETE FROM varints"));
   }
 }
 
 TEST_F(RecoverModuleTest, TextEncodings) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE encodings(t TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE encodings(t TEXT)"));
 
   const std::vector<std::string> values = {
       "",          "a",         u8"ö",       u8"Mjollnir", u8"Mjölnir",
@@ -998,7 +1015,7 @@
   };
 
   sql::Statement insert(
-      db().GetUniqueStatement("INSERT INTO encodings VALUES(?)"));
+      db_.GetUniqueStatement("INSERT INTO encodings VALUES(?)"));
   for (const std::string& value : values) {
     insert.BindString(0, value);
     ASSERT_TRUE(insert.Run());
@@ -1006,10 +1023,10 @@
   }
 
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_encodings "
-                   "USING recover(encodings, t TEXT)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_encodings "
+                  "USING recover(encodings, t TEXT)"));
   sql::Statement select(
-      db().GetUniqueStatement("SELECT rowid, t FROM recover_encodings"));
+      db_.GetUniqueStatement("SELECT rowid, t FROM recover_encodings"));
   for (size_t i = 0; i < values.size(); ++i) {
     ASSERT_TRUE(select.Step());
     EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
@@ -1019,7 +1036,7 @@
 }
 
 TEST_F(RecoverModuleTest, BlobEncodings) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE blob_encodings(t BLOB)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE blob_encodings(t BLOB)"));
 
   const std::vector<std::vector<uint8_t>> values = {
       {},           {0x00},       {0x01},
@@ -1028,7 +1045,7 @@
   };
 
   sql::Statement insert(
-      db().GetUniqueStatement("INSERT INTO blob_encodings VALUES(?)"));
+      db_.GetUniqueStatement("INSERT INTO blob_encodings VALUES(?)"));
   for (const std::vector<uint8_t>& value : values) {
     // std::vector::data() returns nullptr for empty vectors. Unfortunately,
     // sqlite3_bind_blob() always interprets null data as a NULL value. In this
@@ -1043,10 +1060,10 @@
   }
 
   ASSERT_TRUE(
-      db().Execute("CREATE VIRTUAL TABLE temp.recover_blob_encodings "
-                   "USING recover(blob_encodings, t BLOB)"));
+      db_.Execute("CREATE VIRTUAL TABLE temp.recover_blob_encodings "
+                  "USING recover(blob_encodings, t BLOB)"));
   sql::Statement select(
-      db().GetUniqueStatement("SELECT rowid, t FROM recover_blob_encodings"));
+      db_.GetUniqueStatement("SELECT rowid, t FROM recover_blob_encodings"));
   for (size_t i = 0; i < values.size(); ++i) {
     ASSERT_TRUE(select.Step());
     EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
@@ -1113,60 +1130,60 @@
 }  // namespace
 
 TEST_F(RecoverModuleTest, ValueWithoutOverflow) {
-  CheckLargeValueRecovery(&db(), db().page_size() - kRecordOverhead);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(2 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(2 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page and a leaf page";
 }
 
 TEST_F(RecoverModuleTest, ValueWithOneByteOverflow) {
-  CheckLargeValueRecovery(&db(), db().page_size() - kRecordOverhead + 1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + 1);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 1 overflow page";
 }
 
 TEST_F(RecoverModuleTest, ValueWithOneOverflowPage) {
   CheckLargeValueRecovery(
-      &db(), db().page_size() - kRecordOverhead + db().page_size() / 2);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+      &db_, db_.page_size() - kRecordOverhead + db_.page_size() / 2);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 1 overflow page";
 }
 
 TEST_F(RecoverModuleTest, ValueWithOneFullOverflowPage) {
-  CheckLargeValueRecovery(&db(), db().page_size() - kRecordOverhead +
-                                     db().page_size() - kOverflowOverhead);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
+                                    db_.page_size() - kOverflowOverhead);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 1 overflow page";
 }
 
 TEST_F(RecoverModuleTest, ValueWithOneByteSecondOverflowPage) {
-  CheckLargeValueRecovery(&db(), db().page_size() - kRecordOverhead +
-                                     db().page_size() - kOverflowOverhead + 1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
+                                    db_.page_size() - kOverflowOverhead + 1);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 2 overflow pages";
 }
 
 TEST_F(RecoverModuleTest, ValueWithTwoOverflowPages) {
-  CheckLargeValueRecovery(&db(), db().page_size() - kRecordOverhead +
-                                     db().page_size() - kOverflowOverhead +
-                                     db().page_size() / 2);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
+                                    db_.page_size() - kOverflowOverhead +
+                                    db_.page_size() / 2);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 2 overflow pages";
 }
 
 TEST_F(RecoverModuleTest, ValueWithTwoFullOverflowPages) {
   // This value is large enough that the varint encoding of its type ID takes up
   // 3 bytes, instead of 2.
-  CheckLargeValueRecovery(&db(),
-                          db().page_size() - kRecordOverhead +
-                              (db().page_size() - kOverflowOverhead) * 2 - 1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db()) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db()))
+  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
+                                    (db_.page_size() - kOverflowOverhead) * 2 -
+                                    1);
+  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
+  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
       << "Database should have a root page, a leaf page, and 2 overflow pages";
 }
 
diff --git a/sql/recovery_unittest.cc b/sql/recovery_unittest.cc
index afd3865..f402907 100644
--- a/sql/recovery_unittest.cc
+++ b/sql/recovery_unittest.cc
@@ -22,11 +22,12 @@
 #include "sql/statement.h"
 #include "sql/test/paths.h"
 #include "sql/test/scoped_error_expecter.h"
-#include "sql/test/sql_test_base.h"
 #include "sql/test/test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/sqlite/sqlite3.h"
 
+namespace sql {
+
 namespace {
 
 using sql::test::ExecuteWithResult;
@@ -36,81 +37,104 @@
 // schema.  For tables or indices, this will contain the sql command
 // to create the table or index.  For certain automatic SQLite
 // structures with no sql, the name is used.
-std::string GetSchema(sql::Database* db) {
+std::string GetSchema(Database* db) {
   static const char kSql[] =
       "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1";
   return ExecuteWithResults(db, kSql, "|", "\n");
 }
 
-using SQLRecoveryTest = sql::SQLTestBase;
+class SQLRecoveryTest : public testing::Test {
+ public:
+  ~SQLRecoveryTest() override = default;
 
-// Baseline sql::Recovery test covering the different ways to dispose of the
-// scoped pointer received from sql::Recovery::Begin().
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    db_path_ = temp_dir_.GetPath().AppendASCII("recovery_test.sqlite");
+    ASSERT_TRUE(db_.Open(db_path_));
+  }
+
+  bool Reopen() {
+    db_.Close();
+    return db_.Open(db_path_);
+  }
+
+  bool OverwriteDatabaseHeader() {
+    base::File file(db_path_,
+                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    static constexpr char kText[] = "Now is the winter of our discontent.";
+    constexpr int kTextBytes = sizeof(kText) - 1;
+    return file.Write(0, kText, kTextBytes) == kTextBytes;
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  base::FilePath db_path_;
+  Database db_;
+};
+
+// Baseline Recovery test covering the different ways to dispose of the
+// scoped pointer received from Recovery::Begin().
 TEST_F(SQLRecoveryTest, RecoverBasic) {
   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
   static const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
   static const char kAltInsertSql[] =
       "INSERT INTO x VALUES ('That was a test')";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   // If the Recovery handle goes out of scope without being
   // Recovered(), the database is razed.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
   }
-  EXPECT_FALSE(db().is_open());
+  EXPECT_FALSE(db_.is_open());
   ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db().is_open());
-  ASSERT_EQ("", GetSchema(&db()));
+  EXPECT_TRUE(db_.is_open());
+  ASSERT_EQ("", GetSchema(&db_));
 
   // Recreate the database.
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   // Unrecoverable() also razes.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
-    sql::Recovery::Unrecoverable(std::move(recovery));
+    Recovery::Unrecoverable(std::move(recovery));
 
-    // TODO(shess): Test that calls to recover.db() start failing.
+    // TODO(shess): Test that calls to recover.db_ start failing.
   }
-  EXPECT_FALSE(db().is_open());
+  EXPECT_FALSE(db_.is_open());
   ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db().is_open());
-  ASSERT_EQ("", GetSchema(&db()));
+  EXPECT_TRUE(db_.is_open());
+  ASSERT_EQ("", GetSchema(&db_));
 
   // Attempting to recover a previously-recovered handle fails early.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
     recovery.reset();
 
-    recovery = sql::Recovery::Begin(&db(), db_path());
+    recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_FALSE(recovery.get());
   }
   ASSERT_TRUE(Reopen());
 
   // Recreate the database.
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   // Unrecovered table to distinguish from recovered database.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c INTEGER)"));
-  ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c INTEGER)"));
+  ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   // Recovered() replaces the original with the "recovered" version.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
 
     // Create the new version of the table.
@@ -120,50 +144,48 @@
     ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
 
     // Successfully recovered.
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
-  EXPECT_FALSE(db().is_open());
+  EXPECT_FALSE(db_.is_open());
   ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db().is_open());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  EXPECT_TRUE(db_.is_open());
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   const char* kXSql = "SELECT * FROM x ORDER BY 1";
-  ASSERT_EQ("That was a test", ExecuteWithResult(&db(), kXSql));
+  ASSERT_EQ("That was a test", ExecuteWithResult(&db_, kXSql));
 
   // Reset the database contents.
-  ASSERT_TRUE(db().Execute("DELETE FROM x"));
-  ASSERT_TRUE(db().Execute(kInsertSql));
+  ASSERT_TRUE(db_.Execute("DELETE FROM x"));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
 
   // Rollback() discards recovery progress and leaves the database as it was.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
 
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
     ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
 
-    sql::Recovery::Rollback(std::move(recovery));
+    Recovery::Rollback(std::move(recovery));
   }
-  EXPECT_FALSE(db().is_open());
+  EXPECT_FALSE(db_.is_open());
   ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db().is_open());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  EXPECT_TRUE(db_.is_open());
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
-  ASSERT_EQ("This is a test", ExecuteWithResult(&db(), kXSql));
+  ASSERT_EQ("This is a test", ExecuteWithResult(&db_, kXSql));
 }
 
-// Test operation of the virtual table used by sql::Recovery.
+// Test operation of the virtual table used by Recovery.
 TEST_F(SQLRecoveryTest, VirtualTable) {
   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test')"));
 
   // Successfully recover the database.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
 
     // Tables to recover original DB, now at [corrupt].
     static const char kRecoveryCreateSql[] =
@@ -182,32 +204,32 @@
     ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
 
     // Successfully recovered.
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
+  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
 
   static const char* kXSql = "SELECT * FROM x ORDER BY 1";
   ASSERT_EQ("That was a test\nThis is a test",
-            ExecuteWithResults(&db(), kXSql, "|", "\n"));
+            ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
-void RecoveryCallback(sql::Database* db,
+void RecoveryCallback(Database* db,
                       const base::FilePath& db_path,
                       const char* create_table,
                       const char* create_index,
                       int* record_error,
                       int error,
-                      sql::Statement* stmt) {
+                      Statement* stmt) {
   *record_error = error;
 
   // Clear the error callback to prevent reentrancy.
   db->reset_error_callback();
 
-  std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
+  std::unique_ptr<Recovery> recovery = Recovery::Begin(db, db_path);
   ASSERT_TRUE(recovery.get());
 
   ASSERT_TRUE(recovery->db()->Execute(create_table));
@@ -216,7 +238,7 @@
   size_t rows = 0;
   ASSERT_TRUE(recovery->AutoRecoverTable("x", &rows));
 
-  ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+  ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
 }
 
 // Build a database, corrupt it by making an index reference to
@@ -224,15 +246,15 @@
 TEST_F(SQLRecoveryTest, RecoverCorruptIndex) {
   static const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
   static const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
-  ASSERT_TRUE(db().Execute(kCreateTable));
-  ASSERT_TRUE(db().Execute(kCreateIndex));
+  ASSERT_TRUE(db_.Execute(kCreateTable));
+  ASSERT_TRUE(db_.Execute(kCreateIndex));
 
   // Insert a bit of data.
   {
-    ASSERT_TRUE(db().BeginTransaction());
+    ASSERT_TRUE(db_.BeginTransaction());
 
     static const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
-    sql::Statement s(db().GetUniqueStatement(kInsertSql));
+    Statement s(db_.GetUniqueStatement(kInsertSql));
     for (int i = 0; i < 10; ++i) {
       s.Reset(true);
       s.BindInt(0, i);
@@ -241,42 +263,42 @@
       EXPECT_TRUE(s.Succeeded());
     }
 
-    ASSERT_TRUE(db().CommitTransaction());
+    ASSERT_TRUE(db_.CommitTransaction());
   }
-  db().Close();
+  db_.Close();
 
   // Delete a row from the table, while leaving the index entry which
   // references it.
   static const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
-  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql));
+  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path_, "x_id", kDeleteSql));
 
   ASSERT_TRUE(Reopen());
 
   int error = SQLITE_OK;
-  db().set_error_callback(base::BindRepeating(
-      &RecoveryCallback, &db(), db_path(), kCreateTable, kCreateIndex, &error));
+  db_.set_error_callback(base::BindRepeating(
+      &RecoveryCallback, &db_, db_path_, kCreateTable, kCreateIndex, &error));
 
   // This works before the callback is called.
   static const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
-  EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
+  EXPECT_TRUE(db_.IsSQLValid(kTrivialSql));
 
   // TODO(shess): Could this be delete?  Anything which fails should work.
   static const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
-  ASSERT_FALSE(db().Execute(kSelectSql));
+  ASSERT_FALSE(db_.Execute(kSelectSql));
   EXPECT_EQ(SQLITE_CORRUPT, error);
 
   // Database handle has been poisoned.
-  EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
+  EXPECT_FALSE(db_.IsSQLValid(kTrivialSql));
 
   ASSERT_TRUE(Reopen());
 
   // The recovered table should reflect the deletion.
   static const char kSelectAllSql[] = "SELECT v FROM x ORDER BY id";
   EXPECT_EQ("1,2,3,4,5,6,7,8,9",
-            ExecuteWithResults(&db(), kSelectAllSql, "|", ","));
+            ExecuteWithResults(&db_, kSelectAllSql, "|", ","));
 
   // The failing statement should now succeed, with no results.
-  EXPECT_EQ("", ExecuteWithResults(&db(), kSelectSql, "|", ","));
+  EXPECT_EQ("", ExecuteWithResults(&db_, kSelectSql, "|", ","));
 }
 
 // Build a database, corrupt it by making a table contain a row not
@@ -284,15 +306,15 @@
 TEST_F(SQLRecoveryTest, RecoverCorruptTable) {
   static const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
   static const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
-  ASSERT_TRUE(db().Execute(kCreateTable));
-  ASSERT_TRUE(db().Execute(kCreateIndex));
+  ASSERT_TRUE(db_.Execute(kCreateTable));
+  ASSERT_TRUE(db_.Execute(kCreateIndex));
 
   // Insert a bit of data.
   {
-    ASSERT_TRUE(db().BeginTransaction());
+    ASSERT_TRUE(db_.BeginTransaction());
 
     static const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
-    sql::Statement s(db().GetUniqueStatement(kInsertSql));
+    Statement s(db_.GetUniqueStatement(kInsertSql));
     for (int i = 0; i < 10; ++i) {
       s.Reset(true);
       s.BindInt(0, i);
@@ -301,62 +323,62 @@
       EXPECT_TRUE(s.Succeeded());
     }
 
-    ASSERT_TRUE(db().CommitTransaction());
+    ASSERT_TRUE(db_.CommitTransaction());
   }
-  db().Close();
+  db_.Close();
 
   // Delete a row from the index while leaving a table entry.
   static const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
-  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql));
+  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path_, "x", kDeleteSql));
 
   ASSERT_TRUE(Reopen());
 
   int error = SQLITE_OK;
-  db().set_error_callback(base::BindRepeating(
-      &RecoveryCallback, &db(), db_path(), kCreateTable, kCreateIndex, &error));
+  db_.set_error_callback(base::BindRepeating(
+      &RecoveryCallback, &db_, db_path_, kCreateTable, kCreateIndex, &error));
 
   // Index shows one less than originally inserted.
   static const char kCountSql[] = "SELECT COUNT (*) FROM x";
-  EXPECT_EQ("9", ExecuteWithResult(&db(), kCountSql));
+  EXPECT_EQ("9", ExecuteWithResult(&db_, kCountSql));
 
   // A full table scan shows all of the original data.  Using column [v] to
   // force use of the table rather than the index.
   static const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x";
-  EXPECT_EQ("10", ExecuteWithResult(&db(), kDistinctSql));
+  EXPECT_EQ("10", ExecuteWithResult(&db_, kDistinctSql));
 
   // Insert id 0 again.  Since it is not in the index, the insert
   // succeeds, but results in a duplicate value in the table.
   static const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)";
-  ASSERT_TRUE(db().Execute(kInsertSql));
+  ASSERT_TRUE(db_.Execute(kInsertSql));
 
   // Duplication is visible.
-  EXPECT_EQ("10", ExecuteWithResult(&db(), kCountSql));
-  EXPECT_EQ("11", ExecuteWithResult(&db(), kDistinctSql));
+  EXPECT_EQ("10", ExecuteWithResult(&db_, kCountSql));
+  EXPECT_EQ("11", ExecuteWithResult(&db_, kDistinctSql));
 
   // This works before the callback is called.
   static const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
-  EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
+  EXPECT_TRUE(db_.IsSQLValid(kTrivialSql));
 
   // TODO(shess): Figure out a statement which causes SQLite to notice the
   // corruption.  SELECT doesn't see errors because missing index values aren't
   // visible.  UPDATE or DELETE against v=0 don't see errors, even though the
   // index item is missing.  I suspect SQLite only deletes the key in these
   // cases, but doesn't verify that one or more keys were deleted.
-  ASSERT_FALSE(db().Execute("INSERT INTO x (id, v) VALUES (0, 101)"));
+  ASSERT_FALSE(db_.Execute("INSERT INTO x (id, v) VALUES (0, 101)"));
   EXPECT_EQ(SQLITE_CONSTRAINT_UNIQUE, error);
 
   // Database handle has been poisoned.
-  EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
+  EXPECT_FALSE(db_.IsSQLValid(kTrivialSql));
 
   ASSERT_TRUE(Reopen());
 
   // The recovered table has consistency between the index and the table.
-  EXPECT_EQ("10", ExecuteWithResult(&db(), kCountSql));
-  EXPECT_EQ("10", ExecuteWithResult(&db(), kDistinctSql));
+  EXPECT_EQ("10", ExecuteWithResult(&db_, kCountSql));
+  EXPECT_EQ("10", ExecuteWithResult(&db_, kDistinctSql));
 
   // Only one of the values is retained.
   static const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
-  const std::string results = ExecuteWithResult(&db(), kSelectSql);
+  const std::string results = ExecuteWithResult(&db_, kSelectSql);
   EXPECT_TRUE(results=="100" || results=="0") << "Actual results: " << results;
 }
 
@@ -365,45 +387,42 @@
   const int kCompatibleVersion = 2;
 
   {
-    sql::MetaTable meta;
-    EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion));
+    MetaTable meta;
+    EXPECT_TRUE(meta.Init(&db_, kVersion, kCompatibleVersion));
     EXPECT_EQ(kVersion, meta.GetVersionNumber());
   }
 
   // Test expected case where everything works.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     EXPECT_TRUE(recovery->SetupMeta());
     int version = 0;
     EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
     EXPECT_EQ(kVersion, version);
 
-    sql::Recovery::Rollback(std::move(recovery));
+    Recovery::Rollback(std::move(recovery));
   }
   ASSERT_TRUE(Reopen());  // Handle was poisoned.
 
   // Test version row missing.
-  EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'"));
+  EXPECT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'version'"));
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     EXPECT_TRUE(recovery->SetupMeta());
     int version = 0;
     EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
     EXPECT_EQ(0, version);
 
-    sql::Recovery::Rollback(std::move(recovery));
+    Recovery::Rollback(std::move(recovery));
   }
   ASSERT_TRUE(Reopen());  // Handle was poisoned.
 
   // Test meta table missing.
-  EXPECT_TRUE(db().Execute("DROP TABLE meta"));
+  EXPECT_TRUE(db_.Execute("DROP TABLE meta"));
   {
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);  // From virtual table.
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     EXPECT_FALSE(recovery->SetupMeta());
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
@@ -414,23 +433,22 @@
   // BIGINT and VARCHAR to test type affinity.
   static const char kCreateSql[] =
       "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
 
   // Save aside a copy of the original schema and data.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
   // Create a lame-duck table which will not be propagated by recovery to
   // detect that the recovery code actually ran.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(orig_schema, GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
+  ASSERT_NE(orig_schema, GetSchema(&db_));
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
 
     // Save a copy of the temp db's schema before recovering the table.
@@ -447,26 +465,25 @@
     EXPECT_EQ(temp_schema,
               ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
 
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(orig_schema, GetSchema(&db()));
-  ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ(orig_schema, GetSchema(&db_));
+  ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
   // Recovery fails if the target table doesn't exist.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
 
     // TODO(shess): Should this failure implicitly lead to Raze()?
     size_t rows = 0;
     EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
 
-    sql::Recovery::Unrecoverable(std::move(recovery));
+    Recovery::Unrecoverable(std::move(recovery));
   }
 }
 
@@ -474,30 +491,30 @@
 // virtual table reads directly from the database, so DEFAULT is not
 // interpretted at that level.
 TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE x (id INTEGER)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15)"));
 
   // ALTER effectively leaves the new columns NULL in the first two
   // rows.  The row with 17 will get the default injected at insert
   // time, while the row with 42 will get the actual value provided.
   // Embedded "'" to make sure default-handling continues to be quoted
   // correctly.
-  ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
-  ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
-  ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
+  ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
+  ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
+  ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x (id) VALUES (17)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
 
   // Save aside a copy of the original schema and data.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
   // Create a lame-duck table which will not be propagated by recovery to
   // detect that the recovery code actually ran.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(orig_schema, GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
+  ASSERT_NE(orig_schema, GetSchema(&db_));
 
   // Mechanically adjust the stored schema and data to allow detecting
   // where the default value is coming from.  The target table is just
@@ -516,8 +533,7 @@
   }
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     // Different default to detect which table provides the default.
     ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
 
@@ -525,14 +541,14 @@
     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
     EXPECT_EQ(4u, rows);
 
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(final_schema, GetSchema(&db()));
-  ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ(final_schema, GetSchema(&db_));
+  ASSERT_EQ(final_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
 // Test that rows with NULL in a NOT NULL column are filtered
@@ -544,34 +560,33 @@
   static const char kFinalSchema[] =
       "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
 
-  ASSERT_TRUE(db().Execute(kOrigSchema));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, NULL)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')"));
+  ASSERT_TRUE(db_.Execute(kOrigSchema));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, NULL)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15, 'this is a test')"));
 
   // Create a lame-duck table which will not be propagated by recovery to
   // detect that the recovery code actually ran.
-  ASSERT_EQ(kOrigSchema, GetSchema(&db()));
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(kOrigSchema, GetSchema(&db()));
+  ASSERT_EQ(kOrigSchema, GetSchema(&db_));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
+  ASSERT_NE(kOrigSchema, GetSchema(&db_));
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
 
     size_t rows = 0;
     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
     EXPECT_EQ(1u, rows);
 
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // The schema should be the same, but only one row of data should
   // have been recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(kFinalSchema, GetSchema(&db()));
+  ASSERT_EQ(kFinalSchema, GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ("15|this is a test", ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
 // Test AutoRecoverTable with a ROWID alias.
@@ -580,37 +595,36 @@
   // put it later.
   static const char kCreateSql[] =
       "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', NULL)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', NULL)"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test', NULL)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test', NULL)"));
 
   // Save aside a copy of the original schema and data.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
   // Create a lame-duck table which will not be propagated by recovery to
   // detect that the recovery code actually ran.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(orig_schema, GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
+  ASSERT_NE(orig_schema, GetSchema(&db_));
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
 
     size_t rows = 0;
     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
     EXPECT_EQ(2u, rows);
 
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(orig_schema, GetSchema(&db()));
-  ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ(orig_schema, GetSchema(&db_));
+  ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
 // Test that a compound primary key doesn't fire the ROWID code.
@@ -622,41 +636,40 @@
       "t TEXT,"
       "PRIMARY KEY (id, id2)"
       ")";
-  ASSERT_TRUE(db().Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
 
   // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
   // be the ROWID values.
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
 
   // Save aside a copy of the original schema and data.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
   // Create a lame-duck table which will not be propagated by recovery to
   // detect that the recovery code actually ran.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(orig_schema, GetSchema(&db()));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
+  ASSERT_NE(orig_schema, GetSchema(&db_));
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
 
     size_t rows = 0;
     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
     EXPECT_EQ(3u, rows);
 
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(orig_schema, GetSchema(&db()));
-  ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ(orig_schema, GetSchema(&db_));
+  ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
 // Test recovering from a table with fewer columns than the target.
@@ -665,46 +678,45 @@
       "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
   static const char kAlterSql[] =
       "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
-  ASSERT_TRUE(db().Execute(kCreateSql));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')"));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'This is')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'That was')"));
 
   // Generate the expected info by faking a table to match what recovery will
   // create.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
   std::string expected_schema;
   std::string expected_data;
   {
-    ASSERT_TRUE(db().BeginTransaction());
-    ASSERT_TRUE(db().Execute(kAlterSql));
+    ASSERT_TRUE(db_.BeginTransaction());
+    ASSERT_TRUE(db_.Execute(kAlterSql));
 
-    expected_schema = GetSchema(&db());
-    expected_data = ExecuteWithResults(&db(), kXSql, "|", "\n");
+    expected_schema = GetSchema(&db_);
+    expected_data = ExecuteWithResults(&db_, kXSql, "|", "\n");
 
-    db().RollbackTransaction();
+    db_.RollbackTransaction();
   }
 
   // Following tests are pointless if the rollback didn't work.
-  ASSERT_EQ(orig_schema, GetSchema(&db()));
+  ASSERT_EQ(orig_schema, GetSchema(&db_));
 
   // Recover the previous version of the table into the altered version.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
     ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
     size_t rows = 0;
     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
     EXPECT_EQ(2u, rows);
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(expected_schema, GetSchema(&db()));
-  ASSERT_EQ(expected_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_EQ(expected_schema, GetSchema(&db_));
+  ASSERT_EQ(expected_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
 // Recover a golden file where an interior page has been manually modified so
@@ -714,13 +726,12 @@
   base::FilePath golden_path;
   ASSERT_TRUE(base::PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
   golden_path = golden_path.AppendASCII("recovery_387868");
-  db().Close();
-  ASSERT_TRUE(base::CopyFile(golden_path, db_path()));
+  db_.Close();
+  ASSERT_TRUE(base::CopyFile(golden_path, db_path_));
   ASSERT_TRUE(Reopen());
 
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_TRUE(recovery.get());
 
     // Create the new version of the table.
@@ -733,96 +744,95 @@
     EXPECT_EQ(43u, rows);
 
     // Successfully recovered.
-    EXPECT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    EXPECT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
 }
 
 // Memory-mapped I/O interacts poorly with I/O errors.  Make sure the recovery
 // database doesn't accidentally enable it.
 TEST_F(SQLRecoveryTest, NoMmap) {
-  std::unique_ptr<sql::Recovery> recovery =
-      sql::Recovery::Begin(&db(), db_path());
+  std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
   ASSERT_TRUE(recovery.get());
 
   // In the current implementation, the PRAGMA successfully runs with no result
   // rows.  Running with a single result of |0| is also acceptable.
-  sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
+  Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
   EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
 }
 
 TEST_F(SQLRecoveryTest, RecoverDatabase) {
   // As a side effect, AUTOINCREMENT creates the sqlite_sequence table for
   // RecoverDatabase() to handle.
-  ASSERT_TRUE(db().Execute(
+  ASSERT_TRUE(db_.Execute(
       "CREATE TABLE x (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)"));
-  EXPECT_TRUE(db().Execute("INSERT INTO x (v) VALUES ('turtle')"));
-  EXPECT_TRUE(db().Execute("INSERT INTO x (v) VALUES ('truck')"));
-  EXPECT_TRUE(db().Execute("INSERT INTO x (v) VALUES ('trailer')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO x (v) VALUES ('turtle')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO x (v) VALUES ('truck')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO x (v) VALUES ('trailer')"));
 
   // This table needs index and a unique index to work.
-  ASSERT_TRUE(db().Execute("CREATE TABLE y (name TEXT, v TEXT)"));
-  ASSERT_TRUE(db().Execute("CREATE UNIQUE INDEX y_name ON y(name)"));
-  ASSERT_TRUE(db().Execute("CREATE INDEX y_v ON y(v)"));
-  EXPECT_TRUE(db().Execute("INSERT INTO y VALUES ('jim', 'telephone')"));
-  EXPECT_TRUE(db().Execute("INSERT INTO y VALUES ('bob', 'truck')"));
-  EXPECT_TRUE(db().Execute("INSERT INTO y VALUES ('dean', 'trailer')"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE y (name TEXT, v TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX y_name ON y(name)"));
+  ASSERT_TRUE(db_.Execute("CREATE INDEX y_v ON y(v)"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO y VALUES ('jim', 'telephone')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO y VALUES ('bob', 'truck')"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO y VALUES ('dean', 'trailer')"));
 
   // View which is the intersection of [x.v] and [y.v].
-  ASSERT_TRUE(db().Execute(
-      "CREATE VIEW v AS SELECT x.v FROM x, y WHERE x.v = y.v"));
+  ASSERT_TRUE(
+      db_.Execute("CREATE VIEW v AS SELECT x.v FROM x, y WHERE x.v = y.v"));
 
   // When an element is deleted from [x], trigger a delete on [y].  Between the
   // BEGIN and END, [old] stands for the deleted rows from [x].
-  ASSERT_TRUE(db().Execute("CREATE TRIGGER t AFTER DELETE ON x "
-                           "BEGIN DELETE FROM y WHERE y.v = old.v; END"));
+  ASSERT_TRUE(
+      db_.Execute("CREATE TRIGGER t AFTER DELETE ON x "
+                  "BEGIN DELETE FROM y WHERE y.v = old.v; END"));
 
   // Save aside a copy of the original schema, verifying that it has the created
   // items plus the sqlite_sequence table.
-  const std::string orig_schema(GetSchema(&db()));
+  const std::string orig_schema(GetSchema(&db_));
   ASSERT_EQ(6, std::count(orig_schema.begin(), orig_schema.end(), '\n'));
 
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
   static const char kYSql[] = "SELECT * FROM y ORDER BY 1";
   static const char kVSql[] = "SELECT * FROM v ORDER BY 1";
   EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
-            ExecuteWithResults(&db(), kXSql, "|", "\n"));
+            ExecuteWithResults(&db_, kXSql, "|", "\n"));
   EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
-            ExecuteWithResults(&db(), kYSql, "|", "\n"));
-  EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db(), kVSql, "|", "\n"));
+            ExecuteWithResults(&db_, kYSql, "|", "\n"));
+  EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db_, kVSql, "|", "\n"));
 
   // Database handle is valid before recovery, poisoned after.
   static const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
-  EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
-  sql::Recovery::RecoverDatabase(&db(), db_path());
-  EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
+  EXPECT_TRUE(db_.IsSQLValid(kTrivialSql));
+  Recovery::RecoverDatabase(&db_, db_path_);
+  EXPECT_FALSE(db_.IsSQLValid(kTrivialSql));
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
   ASSERT_TRUE(Reopen());
-  ASSERT_EQ(orig_schema, GetSchema(&db()));
+  ASSERT_EQ(orig_schema, GetSchema(&db_));
   EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
-            ExecuteWithResults(&db(), kXSql, "|", "\n"));
+            ExecuteWithResults(&db_, kXSql, "|", "\n"));
   EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
-            ExecuteWithResults(&db(), kYSql, "|", "\n"));
-  EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db(), kVSql, "|", "\n"));
+            ExecuteWithResults(&db_, kYSql, "|", "\n"));
+  EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db_, kVSql, "|", "\n"));
 
   // Test that the trigger works.
-  ASSERT_TRUE(db().Execute("DELETE FROM x WHERE v = 'truck'"));
-  EXPECT_EQ("1|turtle\n3|trailer",
-            ExecuteWithResults(&db(), kXSql, "|", "\n"));
+  ASSERT_TRUE(db_.Execute("DELETE FROM x WHERE v = 'truck'"));
+  EXPECT_EQ("1|turtle\n3|trailer", ExecuteWithResults(&db_, kXSql, "|", "\n"));
   EXPECT_EQ("dean|trailer\njim|telephone",
-            ExecuteWithResults(&db(), kYSql, "|", "\n"));
-  EXPECT_EQ("trailer", ExecuteWithResults(&db(), kVSql, "|", "\n"));
+            ExecuteWithResults(&db_, kYSql, "|", "\n"));
+  EXPECT_EQ("trailer", ExecuteWithResults(&db_, kVSql, "|", "\n"));
 }
 
 // When RecoverDatabase() encounters SQLITE_NOTADB, the database is deleted.
 TEST_F(SQLRecoveryTest, RecoverDatabaseDelete) {
   // Create a valid database, then write junk over the header.  This should lead
   // to SQLITE_NOTADB, which will cause ATTACH to fail.
-  ASSERT_TRUE(db().Execute("CREATE TABLE x (t TEXT)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')"));
-  db().Close();
-  WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE);
+  ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
+  db_.Close();
+  ASSERT_TRUE(OverwriteDatabaseHeader());
 
   {
     sql::test::ScopedErrorExpecter expecter;
@@ -832,74 +842,74 @@
     ASSERT_TRUE(Reopen());
 
     // This should "recover" the database by making it valid, but empty.
-    sql::Recovery::RecoverDatabase(&db(), db_path());
+    Recovery::RecoverDatabase(&db_, db_path_);
 
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 
   // Recovery poisoned the handle, must re-open.
-  db().Close();
+  db_.Close();
   ASSERT_TRUE(Reopen());
 
-  EXPECT_EQ("", GetSchema(&db()));
+  EXPECT_EQ("", GetSchema(&db_));
 }
 
 // Allow callers to validate the database between recovery and commit.
 TEST_F(SQLRecoveryTest, BeginRecoverDatabase) {
   // Create a table with a broken index.
-  ASSERT_TRUE(db().Execute("CREATE TABLE t (id INTEGER PRIMARY KEY, c TEXT)"));
-  ASSERT_TRUE(db().Execute("CREATE UNIQUE INDEX t_id ON t (id)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO t VALUES (1, 'hello world')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO t VALUES (2, 'testing')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO t VALUES (3, 'nope')"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE t (id INTEGER PRIMARY KEY, c TEXT)"));
+  ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX t_id ON t (id)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO t VALUES (1, 'hello world')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO t VALUES (2, 'testing')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO t VALUES (3, 'nope')"));
 
   // Inject corruption into the index.
-  db().Close();
+  db_.Close();
   static const char kDeleteSql[] = "DELETE FROM t WHERE id = 3";
-  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "t_id", kDeleteSql));
+  ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path_, "t_id", kDeleteSql));
   ASSERT_TRUE(Reopen());
 
   // id as read from index.
   static const char kSelectIndexIdSql[] = "SELECT id FROM t INDEXED BY t_id";
-  EXPECT_EQ("1,2,3", ExecuteWithResults(&db(), kSelectIndexIdSql, "|", ","));
+  EXPECT_EQ("1,2,3", ExecuteWithResults(&db_, kSelectIndexIdSql, "|", ","));
 
   // id as read from table.
   static const char kSelectTableIdSql[] = "SELECT id FROM t NOT INDEXED";
-  EXPECT_EQ("1,2", ExecuteWithResults(&db(), kSelectTableIdSql, "|", ","));
+  EXPECT_EQ("1,2", ExecuteWithResults(&db_, kSelectTableIdSql, "|", ","));
 
   // Run recovery code, then rollback.  Database remains the same.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::BeginRecoverDatabase(&db(), db_path());
+    std::unique_ptr<Recovery> recovery =
+        Recovery::BeginRecoverDatabase(&db_, db_path_);
     ASSERT_TRUE(recovery);
-    sql::Recovery::Rollback(std::move(recovery));
+    Recovery::Rollback(std::move(recovery));
   }
-  db().Close();
+  db_.Close();
   ASSERT_TRUE(Reopen());
-  EXPECT_EQ("1,2,3", ExecuteWithResults(&db(), kSelectIndexIdSql, "|", ","));
-  EXPECT_EQ("1,2", ExecuteWithResults(&db(), kSelectTableIdSql, "|", ","));
+  EXPECT_EQ("1,2,3", ExecuteWithResults(&db_, kSelectIndexIdSql, "|", ","));
+  EXPECT_EQ("1,2", ExecuteWithResults(&db_, kSelectTableIdSql, "|", ","));
 
   // Run recovery code, then commit.  The failing row is dropped.
   {
-    std::unique_ptr<sql::Recovery> recovery =
-        sql::Recovery::BeginRecoverDatabase(&db(), db_path());
+    std::unique_ptr<Recovery> recovery =
+        Recovery::BeginRecoverDatabase(&db_, db_path_);
     ASSERT_TRUE(recovery);
-    ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
+    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
   }
-  db().Close();
+  db_.Close();
   ASSERT_TRUE(Reopen());
-  EXPECT_EQ("1,2", ExecuteWithResults(&db(), kSelectIndexIdSql, "|", ","));
-  EXPECT_EQ("1,2", ExecuteWithResults(&db(), kSelectTableIdSql, "|", ","));
+  EXPECT_EQ("1,2", ExecuteWithResults(&db_, kSelectIndexIdSql, "|", ","));
+  EXPECT_EQ("1,2", ExecuteWithResults(&db_, kSelectTableIdSql, "|", ","));
 }
 
 // Test histograms recorded when the invalid database cannot be attached.
 TEST_F(SQLRecoveryTest, AttachFailure) {
   // Create a valid database, then write junk over the header.  This should lead
   // to SQLITE_NOTADB, which will cause ATTACH to fail.
-  ASSERT_TRUE(db().Execute("CREATE TABLE x (t TEXT)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')"));
-  db().Close();
-  WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE);
+  ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
+  db_.Close();
+  ASSERT_TRUE(OverwriteDatabaseHeader());
 
   static const char kEventHistogramName[] = "Sqlite.RecoveryEvents";
   const int kEventEnum = 5;  // RECOVERY_FAILED_ATTACH
@@ -914,8 +924,7 @@
     ASSERT_TRUE(Reopen());
 
     // Begin() should fail.
-    std::unique_ptr<sql::Recovery>
-        recovery = sql::Recovery::Begin(&db(), db_path());
+    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
     ASSERT_FALSE(recovery.get());
 
     ASSERT_TRUE(expecter.SawExpectedErrors());
@@ -942,8 +951,8 @@
 
   const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII(
       base::NumberToString(initial_page_size));
-  sql::Database::Delete(db_path);
-  sql::Database db({.page_size = initial_page_size});
+  Database::Delete(db_path);
+  Database db({.page_size = initial_page_size});
   ASSERT_TRUE(db.Open(db_path));
   ASSERT_TRUE(db.Execute(kCreateSql));
   ASSERT_TRUE(db.Execute(kInsertSql1));
@@ -953,18 +962,17 @@
   db.Close();
 
   // Re-open the database while setting a new |options.page_size| in the object.
-  sql::Database recover_db({.page_size = final_page_size});
+  Database recover_db({.page_size = final_page_size});
   ASSERT_TRUE(recover_db.Open(db_path));
   // Recovery will use the page size set in the database object, which may not
   // match the file's page size.
-  sql::Recovery::RecoverDatabase(&recover_db, db_path);
+  Recovery::RecoverDatabase(&recover_db, db_path);
 
   // Recovery poisoned the handle, must re-open.
   recover_db.Close();
 
   // Make sure the page size is read from the file.
-  sql::Database recovered_db(
-      {.page_size = sql::DatabaseOptions::kDefaultPageSize});
+  Database recovered_db({.page_size = DatabaseOptions::kDefaultPageSize});
   ASSERT_TRUE(recovered_db.Open(db_path));
   ASSERT_EQ(expected_final_page_size,
             ExecuteWithResult(&recovered_db, "PRAGMA page_size"));
@@ -972,34 +980,36 @@
             ExecuteWithResults(&recovered_db, kSelectSql, "|", "\n"));
 }
 
-// Verify that sql::Recovery maintains the page size, and the virtual table
+// Verify that Recovery maintains the page size, and the virtual table
 // works with page sizes other than SQLite's default.  Also verify the case
 // where the default page size has changed.
 TEST_F(SQLRecoveryTest, PageSize) {
   const std::string default_page_size =
-      ExecuteWithResult(&db(), "PRAGMA page_size");
+      ExecuteWithResult(&db_, "PRAGMA page_size");
 
   // Check the default page size first.
   EXPECT_NO_FATAL_FAILURE(TestPageSize(
-      db_path(), sql::DatabaseOptions::kDefaultPageSize, default_page_size,
-      sql::DatabaseOptions::kDefaultPageSize, default_page_size));
+      db_path_, DatabaseOptions::kDefaultPageSize, default_page_size,
+      DatabaseOptions::kDefaultPageSize, default_page_size));
 
   // Sync uses 32k pages.
   EXPECT_NO_FATAL_FAILURE(
-      TestPageSize(db_path(), 32768, "32768", 32768, "32768"));
+      TestPageSize(db_path_, 32768, "32768", 32768, "32768"));
 
   // Many clients use 4k pages.  This is the SQLite default after 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 4096, "4096", 4096, "4096"));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 4096, "4096", 4096, "4096"));
 
   // 1k is the default page size before 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 1024, "1024", 1024, "1024"));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 1024, "1024", 1024, "1024"));
 
   // Databases with no page size specified should recover with the new default
   // page size.  2k has never been the default page size.
   ASSERT_NE("2048", default_page_size);
-  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path(), 2048, "2048",
-                                       sql::DatabaseOptions::kDefaultPageSize,
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
+                                       DatabaseOptions::kDefaultPageSize,
                                        default_page_size));
 }
 
 }  // namespace
+
+}  // namespace sql
diff --git a/sql/sql_memory_dump_provider_unittest.cc b/sql/sql_memory_dump_provider_unittest.cc
index c361253..6929639 100644
--- a/sql/sql_memory_dump_provider_unittest.cc
+++ b/sql/sql_memory_dump_provider_unittest.cc
@@ -4,19 +4,41 @@
 
 #include "sql/sql_memory_dump_provider.h"
 
+#include "base/files/scoped_temp_dir.h"
+#include "base/trace_event/memory_dump_request_args.h"
 #include "base/trace_event/process_memory_dump.h"
-#include "sql/test/sql_test_base.h"
+#include "sql/database.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace sql {
+
 namespace {
-using SQLMemoryDumpProviderTest = sql::SQLTestBase;
-}
+
+class SQLMemoryDumpProviderTest : public testing::Test {
+ public:
+  ~SQLMemoryDumpProviderTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(db_.Open(
+        temp_dir_.GetPath().AppendASCII("memory_dump_provider_test.sqlite")));
+
+    ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  Database db_;
+};
 
 TEST_F(SQLMemoryDumpProviderTest, OnMemoryDump) {
   base::trace_event::MemoryDumpArgs args = {
       base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
   base::trace_event::ProcessMemoryDump pmd(args);
-  ASSERT_TRUE(
-      sql::SqlMemoryDumpProvider::GetInstance()->OnMemoryDump(args, &pmd));
+  ASSERT_TRUE(SqlMemoryDumpProvider::GetInstance()->OnMemoryDump(args, &pmd));
   ASSERT_TRUE(pmd.GetAllocatorDump("sqlite"));
 }
+
+}  // namespace
+
+}  // namespace sql
diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc
index 877ec67..a84a51b 100644
--- a/sql/sqlite_features_unittest.cc
+++ b/sql/sqlite_features_unittest.cc
@@ -8,13 +8,13 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/memory_mapped_file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "build/build_config.h"
 #include "sql/database.h"
 #include "sql/statement.h"
-#include "sql/test/sql_test_base.h"
 #include "sql/test/test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/sqlite/sqlite3.h"
@@ -40,16 +40,18 @@
 
 }  // namespace
 
-class SQLiteFeaturesTest : public sql::SQLTestBase {
+class SQLiteFeaturesTest : public testing::Test {
  public:
-  SQLiteFeaturesTest() : error_(SQLITE_OK) {}
+  ~SQLiteFeaturesTest() override = default;
 
   void SetUp() override {
-    SQLTestBase::SetUp();
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    db_path_ = temp_dir_.GetPath().AppendASCII("sqlite_features_test.sqlite");
+    ASSERT_TRUE(db_.Open(db_path_));
 
     // The error delegate will set |error_| and |sql_text_| when any sqlite
     // statement operation returns an error code.
-    db().set_error_callback(
+    db_.set_error_callback(
         base::BindRepeating(&CaptureErrorCallback, &error_, &sql_text_));
   }
 
@@ -57,15 +59,20 @@
     // If any error happened the original sql statement can be found in
     // |sql_text_|.
     EXPECT_EQ(SQLITE_OK, error_) << sql_text_;
-
-    SQLTestBase::TearDown();
   }
 
-  int error() { return error_; }
+  bool Reopen() {
+    db_.Close();
+    return db_.Open(db_path_);
+  }
 
- private:
+ protected:
+  base::ScopedTempDir temp_dir_;
+  base::FilePath db_path_;
+  Database db_;
+
   // The error code of the most recent error.
-  int error_;
+  int error_ = SQLITE_OK;
   // Original statement which has caused the error.
   std::string sql_text_;
 };
@@ -73,21 +80,20 @@
 // Do not include fts1 support, it is not useful, and nobody is
 // looking at it.
 TEST_F(SQLiteFeaturesTest, NoFTS1) {
-  ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(
-      "CREATE VIRTUAL TABLE foo USING fts1(x)"));
+  ASSERT_EQ(SQLITE_ERROR, db_.ExecuteAndReturnErrorCode(
+                              "CREATE VIRTUAL TABLE foo USING fts1(x)"));
 }
 
 // Do not include fts2 support, it is not useful, and nobody is
 // looking at it.
 TEST_F(SQLiteFeaturesTest, NoFTS2) {
-  ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode(
-      "CREATE VIRTUAL TABLE foo USING fts2(x)"));
+  ASSERT_EQ(SQLITE_ERROR, db_.ExecuteAndReturnErrorCode(
+                              "CREATE VIRTUAL TABLE foo USING fts2(x)"));
 }
 
-// fts3 used to be used for history files, and may also be used by WebDatabase
-// clients.
+// fts3 is exposed in WebSQL.
 TEST_F(SQLiteFeaturesTest, FTS3) {
-  ASSERT_TRUE(db().Execute("CREATE VIRTUAL TABLE foo USING fts3(x)"));
+  ASSERT_TRUE(db_.Execute("CREATE VIRTUAL TABLE foo USING fts3(x)"));
 }
 
 // Originally history used fts2, which Chromium patched to treat "foo*" as a
@@ -96,12 +102,12 @@
 TEST_F(SQLiteFeaturesTest, FTS3_Prefix) {
   static const char kCreateSql[] =
       "CREATE VIRTUAL TABLE foo USING fts3(x, tokenize icu)";
-  ASSERT_TRUE(db().Execute(kCreateSql));
+  ASSERT_TRUE(db_.Execute(kCreateSql));
 
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (x) VALUES ('test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (x) VALUES ('test')"));
 
   EXPECT_EQ("test",
-            ExecuteWithResult(&db(), "SELECT x FROM foo WHERE x MATCH 'te*'"));
+            ExecuteWithResult(&db_, "SELECT x FROM foo WHERE x MATCH 'te*'"));
 }
 
 // Verify that Chromium's SQLite is compiled with HAVE_USLEEP defined.  With
@@ -121,9 +127,9 @@
 // Ensure that our SQLite version has working foreign key support with cascade
 // delete support.
 TEST_F(SQLiteFeaturesTest, ForeignKeySupport) {
-  ASSERT_TRUE(db().Execute("PRAGMA foreign_keys=1"));
-  ASSERT_TRUE(db().Execute("CREATE TABLE parents (id INTEGER PRIMARY KEY)"));
-  ASSERT_TRUE(db().Execute(
+  ASSERT_TRUE(db_.Execute("PRAGMA foreign_keys=1"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE parents (id INTEGER PRIMARY KEY)"));
+  ASSERT_TRUE(db_.Execute(
       "CREATE TABLE children ("
       "    id INTEGER PRIMARY KEY,"
       "    pid INTEGER NOT NULL REFERENCES parents(id) ON DELETE CASCADE)"));
@@ -131,40 +137,40 @@
   static const char kSelectChildrenSql[] = "SELECT * FROM children ORDER BY id";
 
   // Inserting without a matching parent should fail with constraint violation.
-  EXPECT_EQ("", ExecuteWithResult(&db(), kSelectParentsSql));
+  EXPECT_EQ("", ExecuteWithResult(&db_, kSelectParentsSql));
   const int insert_error =
-      db().ExecuteAndReturnErrorCode("INSERT INTO children VALUES (10, 1)");
+      db_.ExecuteAndReturnErrorCode("INSERT INTO children VALUES (10, 1)");
   EXPECT_EQ(SQLITE_CONSTRAINT | SQLITE_CONSTRAINT_FOREIGNKEY, insert_error);
-  EXPECT_EQ("", ExecuteWithResult(&db(), kSelectChildrenSql));
+  EXPECT_EQ("", ExecuteWithResult(&db_, kSelectChildrenSql));
 
   // Inserting with a matching parent should work.
-  ASSERT_TRUE(db().Execute("INSERT INTO parents VALUES (1)"));
-  EXPECT_EQ("1", ExecuteWithResults(&db(), kSelectParentsSql, "|", "\n"));
-  EXPECT_TRUE(db().Execute("INSERT INTO children VALUES (11, 1)"));
-  EXPECT_TRUE(db().Execute("INSERT INTO children VALUES (12, 1)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO parents VALUES (1)"));
+  EXPECT_EQ("1", ExecuteWithResults(&db_, kSelectParentsSql, "|", "\n"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO children VALUES (11, 1)"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO children VALUES (12, 1)"));
   EXPECT_EQ("11|1\n12|1",
-            ExecuteWithResults(&db(), kSelectChildrenSql, "|", "\n"));
+            ExecuteWithResults(&db_, kSelectChildrenSql, "|", "\n"));
 
   // Deleting the parent should cascade, deleting the children as well.
-  ASSERT_TRUE(db().Execute("DELETE FROM parents"));
-  EXPECT_EQ("", ExecuteWithResult(&db(), kSelectParentsSql));
-  EXPECT_EQ("", ExecuteWithResult(&db(), kSelectChildrenSql));
+  ASSERT_TRUE(db_.Execute("DELETE FROM parents"));
+  EXPECT_EQ("", ExecuteWithResult(&db_, kSelectParentsSql));
+  EXPECT_EQ("", ExecuteWithResult(&db_, kSelectChildrenSql));
 }
 
 // Ensure that our SQLite version supports booleans.
 TEST_F(SQLiteFeaturesTest, BooleanSupport) {
   ASSERT_TRUE(
-      db().Execute("CREATE TABLE flags ("
-                   "    id INTEGER PRIMARY KEY,"
-                   "    true_flag BOOL NOT NULL DEFAULT TRUE,"
-                   "    false_flag BOOL NOT NULL DEFAULT FALSE)"));
-  ASSERT_TRUE(db().Execute(
+      db_.Execute("CREATE TABLE flags ("
+                  "    id INTEGER PRIMARY KEY,"
+                  "    true_flag BOOL NOT NULL DEFAULT TRUE,"
+                  "    false_flag BOOL NOT NULL DEFAULT FALSE)"));
+  ASSERT_TRUE(db_.Execute(
       "ALTER TABLE flags ADD COLUMN true_flag2 BOOL NOT NULL DEFAULT TRUE"));
-  ASSERT_TRUE(db().Execute(
+  ASSERT_TRUE(db_.Execute(
       "ALTER TABLE flags ADD COLUMN false_flag2 BOOL NOT NULL DEFAULT FALSE"));
-  ASSERT_TRUE(db().Execute("INSERT INTO flags (id) VALUES (1)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO flags (id) VALUES (1)"));
 
-  sql::Statement s(db().GetUniqueStatement(
+  sql::Statement s(db_.GetUniqueStatement(
       "SELECT true_flag, false_flag, true_flag2, false_flag2"
       "    FROM flags WHERE id=1;"));
   ASSERT_TRUE(s.Step());
@@ -177,13 +183,11 @@
 }
 
 TEST_F(SQLiteFeaturesTest, IcuEnabled) {
-  sql::Statement lower_en(
-      db().GetUniqueStatement("SELECT lower('I', 'en_us')"));
+  sql::Statement lower_en(db_.GetUniqueStatement("SELECT lower('I', 'en_us')"));
   ASSERT_TRUE(lower_en.Step());
   EXPECT_EQ("i", lower_en.ColumnString(0));
 
-  sql::Statement lower_tr(
-      db().GetUniqueStatement("SELECT lower('I', 'tr_tr')"));
+  sql::Statement lower_tr(db_.GetUniqueStatement("SELECT lower('I', 'tr_tr')"));
   ASSERT_TRUE(lower_tr.Step());
   EXPECT_EQ("\u0131", lower_tr.ColumnString(0));
 }
@@ -196,14 +200,14 @@
 // be disabled on this platform using SQLITE_MAX_MMAP_SIZE=0.
 TEST_F(SQLiteFeaturesTest, Mmap) {
   // Try to turn on mmap'ed I/O.
-  ignore_result(db().Execute("PRAGMA mmap_size = 1048576"));
+  ignore_result(db_.Execute("PRAGMA mmap_size = 1048576"));
   {
-    sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
+    sql::Statement s(db_.GetUniqueStatement("PRAGMA mmap_size"));
 
     ASSERT_TRUE(s.Step());
     ASSERT_GT(s.ColumnInt64(0), 0);
   }
-  db().Close();
+  db_.Close();
 
   const uint32_t kFlags =
       base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE;
@@ -211,7 +215,7 @@
 
   // Create a file with a block of '0', a block of '1', and a block of '2'.
   {
-    base::File f(db_path(), kFlags);
+    base::File f(db_path_, kFlags);
     ASSERT_TRUE(f.IsValid());
     memset(buf, '0', sizeof(buf));
     ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
@@ -226,7 +230,7 @@
   // mmap the file and verify that everything looks right.
   {
     base::MemoryMappedFile m;
-    ASSERT_TRUE(m.Initialize(db_path()));
+    ASSERT_TRUE(m.Initialize(db_path_));
 
     memset(buf, '0', sizeof(buf));
     ASSERT_EQ(0, memcmp(buf, m.data() + 0*sizeof(buf), sizeof(buf)));
@@ -240,7 +244,7 @@
     // Scribble some '3' into the first page of the file, and verify that it
     // looks the same in the memory mapping.
     {
-      base::File f(db_path(), kFlags);
+      base::File f(db_path_, kFlags);
       ASSERT_TRUE(f.IsValid());
       memset(buf, '3', sizeof(buf));
       ASSERT_EQ(f.Write(0*sizeof(buf), buf, sizeof(buf)), (int)sizeof(buf));
@@ -251,7 +255,7 @@
     const size_t kOffset = 1*sizeof(buf) + 123;
     ASSERT_NE('4', m.data()[kOffset]);
     {
-      base::File f(db_path(), kFlags);
+      base::File f(db_path_, kFlags);
       ASSERT_TRUE(f.IsValid());
       buf[0] = '4';
       ASSERT_EQ(f.Write(kOffset, buf, 1), 1);
@@ -264,14 +268,14 @@
 // compiled regular expression is effectively cached with the prepared
 // statement, causing errors if the regular expression is rebound.
 TEST_F(SQLiteFeaturesTest, CachedRegexp) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE r (id INTEGER UNIQUE, x TEXT)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (1, 'this is a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (2, 'that was a test')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (3, 'this is a stickup')"));
-  ASSERT_TRUE(db().Execute("INSERT INTO r VALUES (4, 'that sucks')"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE r (id INTEGER UNIQUE, x TEXT)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO r VALUES (1, 'this is a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO r VALUES (2, 'that was a test')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO r VALUES (3, 'this is a stickup')"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO r VALUES (4, 'that sucks')"));
 
   static const char kSimpleSql[] = "SELECT SUM(id) FROM r WHERE x REGEXP ?";
-  sql::Statement s(db().GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
+  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kSimpleSql));
 
   s.BindString(0, "this.*");
   ASSERT_TRUE(s.Step());
@@ -297,26 +301,26 @@
 // If a database file is marked to be excluded from Time Machine, verify that
 // journal files are also excluded.
 TEST_F(SQLiteFeaturesTest, TimeMachine) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE t (id INTEGER PRIMARY KEY)"));
-  db().Close();
+  ASSERT_TRUE(db_.Execute("CREATE TABLE t (id INTEGER PRIMARY KEY)"));
+  db_.Close();
 
-  base::FilePath journal_path = sql::Database::JournalPath(db_path());
-  ASSERT_TRUE(GetPathExists(db_path()));
-  ASSERT_TRUE(GetPathExists(journal_path));
+  base::FilePath journal_path = sql::Database::JournalPath(db_path_);
+  ASSERT_TRUE(base::PathExists(db_path_));
+  ASSERT_TRUE(base::PathExists(journal_path));
 
   // Not excluded to start.
-  EXPECT_FALSE(base::mac::GetFileBackupExclusion(db_path()));
+  EXPECT_FALSE(base::mac::GetFileBackupExclusion(db_path_));
   EXPECT_FALSE(base::mac::GetFileBackupExclusion(journal_path));
 
   // Exclude the main database file.
-  EXPECT_TRUE(base::mac::SetFileBackupExclusion(db_path()));
+  EXPECT_TRUE(base::mac::SetFileBackupExclusion(db_path_));
 
-  EXPECT_TRUE(base::mac::GetFileBackupExclusion(db_path()));
+  EXPECT_TRUE(base::mac::GetFileBackupExclusion(db_path_));
   EXPECT_FALSE(base::mac::GetFileBackupExclusion(journal_path));
 
-  EXPECT_TRUE(db().Open(db_path()));
-  ASSERT_TRUE(db().Execute("INSERT INTO t VALUES (1)"));
-  EXPECT_TRUE(base::mac::GetFileBackupExclusion(db_path()));
+  EXPECT_TRUE(db_.Open(db_path_));
+  ASSERT_TRUE(db_.Execute("INSERT INTO t VALUES (1)"));
+  EXPECT_TRUE(base::mac::GetFileBackupExclusion(db_path_));
   EXPECT_TRUE(base::mac::GetFileBackupExclusion(journal_path));
 
   // TODO(shess): In WAL mode this will touch -wal and -shm files.  -shm files
@@ -329,30 +333,30 @@
 // additional work into Chromium shutdown.  Verify that SQLite supports a config
 // option to not checkpoint on close.
 TEST_F(SQLiteFeaturesTest, WALNoClose) {
-  base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path());
+  base::FilePath wal_path = sql::Database::WriteAheadLogPath(db_path_);
 
   // Turn on WAL mode, then verify that the mode changed (WAL is supported).
-  ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL"));
-  ASSERT_EQ("wal", ExecuteWithResult(&db(), "PRAGMA journal_mode"));
+  ASSERT_TRUE(db_.Execute("PRAGMA journal_mode = WAL"));
+  ASSERT_EQ("wal", ExecuteWithResult(&db_, "PRAGMA journal_mode"));
 
   // The WAL file is created lazily on first change.
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
 
   // By default, the WAL is checkpointed then deleted on close.
-  ASSERT_TRUE(GetPathExists(wal_path));
-  db().Close();
-  ASSERT_FALSE(GetPathExists(wal_path));
+  ASSERT_TRUE(base::PathExists(wal_path));
+  db_.Close();
+  ASSERT_FALSE(base::PathExists(wal_path));
 
   // Reopen and configure the database to not checkpoint WAL on close.
   ASSERT_TRUE(Reopen());
-  ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL"));
-  ASSERT_TRUE(db().Execute("ALTER TABLE foo ADD COLUMN c"));
-  ASSERT_EQ(SQLITE_OK,
-            sqlite3_db_config(db().db_, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1,
-                              nullptr));
-  ASSERT_TRUE(GetPathExists(wal_path));
-  db().Close();
-  ASSERT_TRUE(GetPathExists(wal_path));
+  ASSERT_TRUE(db_.Execute("PRAGMA journal_mode = WAL"));
+  ASSERT_TRUE(db_.Execute("ALTER TABLE foo ADD COLUMN c"));
+  ASSERT_EQ(
+      SQLITE_OK,
+      sqlite3_db_config(db_.db_, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, nullptr));
+  ASSERT_TRUE(base::PathExists(wal_path));
+  db_.Close();
+  ASSERT_TRUE(base::PathExists(wal_path));
 }
 #endif
 
diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc
index 2033ee3..af85326 100644
--- a/sql/statement_unittest.cc
+++ b/sql/statement_unittest.cc
@@ -11,29 +11,40 @@
 #include "sql/statement.h"
 #include "sql/test/error_callback_support.h"
 #include "sql/test/scoped_error_expecter.h"
-#include "sql/test/sql_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/sqlite/sqlite3.h"
 
+namespace sql {
 namespace {
 
-using SQLStatementTest = sql::SQLTestBase;
+class SQLStatementTest : public testing::Test {
+ public:
+  ~SQLStatementTest() override = default;
 
-}  // namespace
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(
+        db_.Open(temp_dir_.GetPath().AppendASCII("statement_test.sqlite")));
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  Database db_;
+};
 
 TEST_F(SQLStatementTest, Assign) {
-  sql::Statement s;
+  Statement s;
   EXPECT_FALSE(s.is_valid());
 
-  s.Assign(db().GetUniqueStatement("CREATE TABLE foo (a, b)"));
+  s.Assign(db_.GetUniqueStatement("CREATE TABLE foo (a, b)"));
   EXPECT_TRUE(s.is_valid());
 }
 
 TEST_F(SQLStatementTest, Run) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
 
-  sql::Statement s(db().GetUniqueStatement("SELECT b FROM foo WHERE a=?"));
+  Statement s(db_.GetUniqueStatement("SELECT b FROM foo WHERE a=?"));
   EXPECT_FALSE(s.Succeeded());
 
   // Stepping it won't work since we haven't bound the value.
@@ -44,7 +55,7 @@
   s.Reset(true);
   s.BindInt(0, 3);
   EXPECT_FALSE(s.Run());
-  EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
+  EXPECT_EQ(SQLITE_ROW, db_.GetErrorCode());
   EXPECT_TRUE(s.Succeeded());
 
   // Resetting it should put it back to the previous state (not runnable).
@@ -62,16 +73,16 @@
 
 // Error callback called for error running a statement.
 TEST_F(SQLStatementTest, ErrorCallback) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
 
   int error = SQLITE_OK;
-  sql::ScopedErrorCallback sec(
-      &db(), base::BindRepeating(&sql::CaptureErrorCallback, &error));
+  ScopedErrorCallback sec(&db_,
+                          base::BindRepeating(&CaptureErrorCallback, &error));
 
   // Insert in the foo table the primary key. It is an error to insert
   // something other than an number. This error causes the error callback
   // handler to be called with SQLITE_MISMATCH as error code.
-  sql::Statement s(db().GetUniqueStatement("INSERT INTO foo (a) VALUES (?)"));
+  Statement s(db_.GetUniqueStatement("INSERT INTO foo (a) VALUES (?)"));
   EXPECT_TRUE(s.is_valid());
   s.BindCString(0, "bad bad");
   EXPECT_FALSE(s.Run());
@@ -80,9 +91,9 @@
 
 // Error expecter works for error running a statement.
 TEST_F(SQLStatementTest, ScopedIgnoreError) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
 
-  sql::Statement s(db().GetUniqueStatement("INSERT INTO foo (a) VALUES (?)"));
+  Statement s(db_.GetUniqueStatement("INSERT INTO foo (a) VALUES (?)"));
   EXPECT_TRUE(s.is_valid());
 
   {
@@ -95,12 +106,11 @@
 }
 
 TEST_F(SQLStatementTest, Reset) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
+  ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+  ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
 
-  sql::Statement s(db().GetUniqueStatement(
-      "SELECT b FROM foo WHERE a = ? "));
+  Statement s(db_.GetUniqueStatement("SELECT b FROM foo WHERE a = ? "));
   s.BindInt(0, 3);
   ASSERT_TRUE(s.Step());
   EXPECT_EQ(12, s.ColumnInt(0));
@@ -115,3 +125,6 @@
   s.Reset(true);
   ASSERT_FALSE(s.Step());
 }
+
+}  // namespace
+}  // namespace sql
diff --git a/sql/test/sql_test_base.cc b/sql/test/sql_test_base.cc
deleted file mode 100644
index ef32741..0000000
--- a/sql/test/sql_test_base.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/test/sql_test_base.h"
-
-#include "base/files/file_util.h"
-#include "sql/test/test_helpers.h"
-
-namespace sql {
-
-SQLTestBase::SQLTestBase() = default;
-
-SQLTestBase::SQLTestBase(sql::DatabaseOptions options) : db_(options) {}
-
-SQLTestBase::~SQLTestBase() = default;
-
-base::FilePath SQLTestBase::db_path() {
-  return temp_dir_.GetPath().AppendASCII("SQLTest.db");
-}
-
-sql::Database& SQLTestBase::db() {
-  return db_;
-}
-
-bool SQLTestBase::Reopen() {
-  db_.Close();
-  return db_.Open(db_path());
-}
-
-bool SQLTestBase::GetPathExists(const base::FilePath& path) {
-  return base::PathExists(path);
-}
-
-bool SQLTestBase::CorruptSizeInHeaderOfDB() {
-  return sql::test::CorruptSizeInHeader(db_path());
-}
-
-void SQLTestBase::WriteJunkToDatabase(WriteJunkType type) {
-  base::ScopedFILE file(base::OpenFile(
-      db_path(),
-      type == TYPE_OVERWRITE_AND_TRUNCATE ? "wb" : "rb+"));
-  ASSERT_TRUE(file.get());
-  ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET));
-
-  const char* kJunk = "Now is the winter of our discontent.";
-  fputs(kJunk, file.get());
-}
-
-void SQLTestBase::TruncateDatabase() {
-  base::ScopedFILE file(base::OpenFile(db_path(), "rb+"));
-  ASSERT_TRUE(file);
-  ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET));
-  ASSERT_TRUE(base::TruncateFile(file.get()));
-}
-
-void SQLTestBase::SetUp() {
-  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  ASSERT_TRUE(db_.Open(db_path()));
-}
-
-void SQLTestBase::TearDown() {
-  db_.Close();
-}
-
-}  // namespace sql
diff --git a/sql/test/sql_test_base.h b/sql/test/sql_test_base.h
deleted file mode 100644
index cf35f1e..0000000
--- a/sql/test/sql_test_base.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_TEST_SQL_TEST_BASE_H_
-#define SQL_TEST_SQL_TEST_BASE_H_
-
-#include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/macros.h"
-#include "sql/database.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace sql {
-
-// Base class for SQL tests.
-//
-// WARNING: We want to run the same gtest based unit test code both against
-// chromium (which uses this implementation here), and the mojo code (which
-// uses a different class named SQLTestBase). These two classes need to have
-// the same interface because we compile time switch them based on a
-// #define. We need to have two different implementations because the mojo
-// version derives from mojo::test::ApplicationTestBase instead of
-// testing::Test.
-class SQLTestBase : public testing::Test {
- public:
-  SQLTestBase();
-  explicit SQLTestBase(sql::DatabaseOptions options);
-  ~SQLTestBase() override;
-
-  enum WriteJunkType {
-    TYPE_OVERWRITE_AND_TRUNCATE,
-    TYPE_OVERWRITE
-  };
-
-  // Returns the path to the database.
-  base::FilePath db_path();
-
-  // Returns a connection to the database at db_path().
-  sql::Database& db();
-
-  // Closes the current connection to the database and reopens it.
-  bool Reopen();
-
-  // Proxying method around base::PathExists.
-  bool GetPathExists(const base::FilePath& path);
-
-  // SQLite stores the database size in the header, and if the actual
-  // OS-derived size is smaller, the database is considered corrupt.
-  // [This case is actually a common form of corruption in the wild.]
-  // This helper sets the in-header size to one page larger than the
-  // actual file size.  The resulting file will return SQLITE_CORRUPT
-  // for most operations unless PRAGMA writable_schema is turned ON.
-  //
-  // Returns false if any error occurs accessing the file.
-  bool CorruptSizeInHeaderOfDB();
-
-  // Writes junk to the start of the file.
-  void WriteJunkToDatabase(WriteJunkType type);
-
-  // Sets the database file size to 0.
-  void TruncateDatabase();
-
-  // Overridden from testing::Test:
-  void SetUp() override;
-  void TearDown() override;
-
- private:
-  base::ScopedTempDir temp_dir_;
-  sql::Database db_;
-
-  DISALLOW_COPY_AND_ASSIGN(SQLTestBase);
-};
-
-}  // namespace sql
-
-#endif  // SQL_TEST_SQL_TEST_BASE_H_
diff --git a/sql/transaction_unittest.cc b/sql/transaction_unittest.cc
index bcc05f58..48f75da2 100644
--- a/sql/transaction_unittest.cc
+++ b/sql/transaction_unittest.cc
@@ -3,38 +3,50 @@
 // found in the LICENSE file.
 
 #include "sql/transaction.h"
+
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "sql/database.h"
 #include "sql/statement.h"
-#include "sql/test/sql_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/sqlite/sqlite3.h"
 
-class SQLTransactionTest : public sql::SQLTestBase {
- public:
-  void SetUp() override {
-    SQLTestBase::SetUp();
+namespace sql {
 
-    ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+namespace {
+
+class SQLTransactionTest : public testing::Test {
+ public:
+  ~SQLTransactionTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(
+        db_.Open(temp_dir_.GetPath().AppendASCII("transaction_test.sqlite")));
+
+    ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)"));
   }
 
   // Returns the number of rows in table "foo".
   int CountFoo() {
-    sql::Statement count(db().GetUniqueStatement("SELECT count(*) FROM foo"));
+    Statement count(db_.GetUniqueStatement("SELECT count(*) FROM foo"));
     count.Step();
     return count.ColumnInt(0);
   }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  Database db_;
 };
 
 TEST_F(SQLTransactionTest, Commit) {
   {
-    sql::Transaction t(&db());
+    Transaction t(&db_);
     EXPECT_FALSE(t.is_open());
     EXPECT_TRUE(t.Begin());
     EXPECT_TRUE(t.is_open());
 
-    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+    EXPECT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
 
     t.Commit();
     EXPECT_FALSE(t.is_open());
@@ -47,23 +59,23 @@
   // Test some basic initialization, and that rollback runs when you exit the
   // scope.
   {
-    sql::Transaction t(&db());
+    Transaction t(&db_);
     EXPECT_FALSE(t.is_open());
     EXPECT_TRUE(t.Begin());
     EXPECT_TRUE(t.is_open());
 
-    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+    EXPECT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
   }
 
   // Nothing should have been committed since it was implicitly rolled back.
   EXPECT_EQ(0, CountFoo());
 
   // Test explicit rollback.
-  sql::Transaction t2(&db());
+  Transaction t2(&db_);
   EXPECT_FALSE(t2.is_open());
   EXPECT_TRUE(t2.Begin());
 
-  EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+  EXPECT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
   t2.Rollback();
   EXPECT_FALSE(t2.is_open());
 
@@ -73,23 +85,23 @@
 
 // Rolling back any part of a transaction should roll back all of them.
 TEST_F(SQLTransactionTest, NestedRollback) {
-  EXPECT_EQ(0, db().transaction_nesting());
+  EXPECT_EQ(0, db_.transaction_nesting());
 
   // Outermost transaction.
   {
-    sql::Transaction outer(&db());
+    Transaction outer(&db_);
     EXPECT_TRUE(outer.Begin());
-    EXPECT_EQ(1, db().transaction_nesting());
+    EXPECT_EQ(1, db_.transaction_nesting());
 
     // The first inner one gets committed.
     {
-      sql::Transaction inner1(&db());
+      Transaction inner1(&db_);
       EXPECT_TRUE(inner1.Begin());
-      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-      EXPECT_EQ(2, db().transaction_nesting());
+      EXPECT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+      EXPECT_EQ(2, db_.transaction_nesting());
 
       inner1.Commit();
-      EXPECT_EQ(1, db().transaction_nesting());
+      EXPECT_EQ(1, db_.transaction_nesting());
     }
 
     // One row should have gotten inserted.
@@ -97,24 +109,28 @@
 
     // The second inner one gets rolled back.
     {
-      sql::Transaction inner2(&db());
+      Transaction inner2(&db_);
       EXPECT_TRUE(inner2.Begin());
-      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-      EXPECT_EQ(2, db().transaction_nesting());
+      EXPECT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+      EXPECT_EQ(2, db_.transaction_nesting());
 
       inner2.Rollback();
-      EXPECT_EQ(1, db().transaction_nesting());
+      EXPECT_EQ(1, db_.transaction_nesting());
     }
 
     // A third inner one will fail in Begin since one has already been rolled
     // back.
-    EXPECT_EQ(1, db().transaction_nesting());
+    EXPECT_EQ(1, db_.transaction_nesting());
     {
-      sql::Transaction inner3(&db());
+      Transaction inner3(&db_);
       EXPECT_FALSE(inner3.Begin());
-      EXPECT_EQ(1, db().transaction_nesting());
+      EXPECT_EQ(1, db_.transaction_nesting());
     }
   }
-  EXPECT_EQ(0, db().transaction_nesting());
+  EXPECT_EQ(0, db_.transaction_nesting());
   EXPECT_EQ(0, CountFoo());
 }
+
+}  // namespace
+
+}  // namespace sql
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 0e750f1f..39dd920 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -49914,23 +49914,6 @@
         "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
       },
       {
-        "args": [
-          "angle_unittests"
-        ],
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "angle_unittests",
-        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
-        "use_isolated_scripts_api": true
-      },
-      {
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50075,6 +50058,9 @@
         "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.browser_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50196,6 +50182,9 @@
         "test_id_prefix": "ninja://components:components_browsertests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.components_unittests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50222,6 +50211,9 @@
         "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.content_browsertests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50327,6 +50319,9 @@
         "test_id_prefix": "ninja://ui/events:events_unittests/"
       },
       {
+        "args": [
+          "--gtest_filter=-BluetoothShellApiTest.ApiSanityCheck:BluetoothSocketApiTest.Listen:BluetoothSocketApiTest.PermissionDenied"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50340,6 +50335,9 @@
         "test_id_prefix": "ninja://extensions:extensions_browsertests/"
       },
       {
+        "args": [
+          "--gtest_filter=-NativeExtensionBindingsSystemUnittest*:BluetoothSocketApiUnittest.CreateThenClose:FeatureProviderTest.PermissionFeatureAvailability"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50444,6 +50442,9 @@
         "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50848,6 +50849,9 @@
         "test_id_prefix": "ninja://storage:storage_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.sync_integration_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50900,6 +50904,9 @@
         "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.unit_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -50926,6 +50933,9 @@
         "test_id_prefix": "ninja://url:url_unittests/"
       },
       {
+        "args": [
+          "--gtest_filter=-DesktopWidgetFocusManagerTest.AnchoredDialogInDesktopNativeWidgetAura:DesktopWidgetTest*:DesktopWindowTreeHostPlatformTest*:EditableComboboxTest*:MenuRunnerTest*:TextfieldTest*:TooltipControllerTest*:TooltipStateManagerTest*"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/filters/linux_bionic.browser_tests.filter b/testing/buildbot/filters/linux_bionic.browser_tests.filter
index 51aca67..a9c19c7 100644
--- a/testing/buildbot/filters/linux_bionic.browser_tests.filter
+++ b/testing/buildbot/filters/linux_bionic.browser_tests.filter
@@ -15,3 +15,4 @@
 -SafeBrowsingNetworkContext/NetworkContextConfigurationFtpPacBrowserTest.FtpPac/1
 -SystemNetworkContext/NetworkContextConfigurationFtpPacBrowserTest.FtpPac/0
 -SystemNetworkContext/NetworkContextConfigurationFtpPacBrowserTest.FtpPac/1
+-WPADHttpProxyScriptBrowserTest.Verify
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index f5a0268..c289b111f 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -131,6 +131,7 @@
       # Times out listing tests crbug.com/1167314
       'android-asan',
       # Does not currently work on Lacros configurations.
+      'linux-lacros-code-coverage',
       'linux-lacros-tester-rel',
       'linux-lacros-tester-fyi-rel',
     ],
@@ -742,6 +743,11 @@
           'shards': 50,
         },
       },
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.browser_tests.filter',
+        ],
+      },
       'linux-lacros-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.browser_tests.filter',
@@ -1148,6 +1154,12 @@
         },
       },
       # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.components_unittests.filter',
+        ],
+      },
+      # https://crbug.com/1111979,
       'linux-lacros-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.components_unittests.filter',
@@ -1318,6 +1330,12 @@
           'shards': 12,
         },
       },
+      # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.content_browsertests.filter',
+        ],
+      },
       # https://crbug.com/1111979
       'linux-lacros-tester-fyi-rel': {
         'experiment_percentage': 100,
@@ -1480,6 +1498,11 @@
         'experiment_percentage': 100, # https://crbug.com/876615
       },
       # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--gtest_filter=-BluetoothShellApiTest.ApiSanityCheck:BluetoothSocketApiTest.Listen:BluetoothSocketApiTest.PermissionDenied',
+        ],
+      },
       'linux-lacros-rel': {
         'args': [
           '--gtest_filter=-BluetoothShellApiTest.ApiSanityCheck:BluetoothSocketApiTest.Listen:BluetoothSocketApiTest.PermissionDenied',
@@ -1495,6 +1518,13 @@
   'extensions_unittests': {
     'modifications': {
       # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--gtest_filter=-NativeExtensionBindingsSystemUnittest*:'
+          'BluetoothSocketApiUnittest.CreateThenClose:'
+          'FeatureProviderTest.PermissionFeatureAvailability',
+        ],
+      },
       'linux-lacros-tester-rel': {
         'args': [
           '--gtest_filter=-NativeExtensionBindingsSystemUnittest*:'
@@ -1835,6 +1865,12 @@
         },
       },
       # https://crbug.com/1111979
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter',
+        ],
+      },
+      # https://crbug.com/1111979
       'linux-lacros-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter',
@@ -2686,6 +2722,11 @@
         },
       },
       # https://crbug.com/1199909,
+      'linux-lacros-code-coverage': {
+        'args': [
+           '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.sync_integration_tests.filter',
+        ]
+      },
       'linux-lacros-rel': {
         'args': [
            '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.sync_integration_tests.filter',
@@ -2936,6 +2977,12 @@
         },
       },
       # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.unit_tests.filter',
+        ],
+      },
+      # https://crbug.com/1111979,
       'linux-lacros-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.unit_tests.filter',
@@ -2970,6 +3017,18 @@
   'views_unittests': {
     'modifications': {
       # https://crbug.com/1111979,
+      'linux-lacros-code-coverage': {
+        'args': [
+          '--gtest_filter=-DesktopWidgetFocusManagerTest.AnchoredDialogInDesktopNativeWidgetAura:'
+          'DesktopWidgetTest*:'
+          'DesktopWindowTreeHostPlatformTest*:'
+          'EditableComboboxTest*:'
+          'MenuRunnerTest*:'
+          'TextfieldTest*:'
+          'TooltipControllerTest*:'
+          'TooltipStateManagerTest*'
+        ],
+      },
       'linux-lacros-tester-rel': {
         'args': [
           '--gtest_filter=-DesktopWidgetFocusManagerTest.AnchoredDialogInDesktopNativeWidgetAura:'
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 682cc032..56b89f4 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -503,10 +503,14 @@
            'outputing structured test results and perf results output:')
     print traceback.format_exc()
   finally:
-    # Add ignore_errors=True because otherwise rmtree may fail due to leaky
-    # processes of tests are still holding opened handles to files under
-    # |tempfile_dir|. For example, see crbug.com/865896
-    shutil.rmtree(temp_dir, ignore_errors=True)
+    # On swarming bots, don't remove output directory, since Result Sink might
+    # still be uploading files to Result DB. Also, swarming bots automatically
+    # clean up at the end of each task.
+    if 'SWARMING_TASK_ID' not in os.environ:
+      # Add ignore_errors=True because otherwise rmtree may fail due to leaky
+      # processes of tests are still holding opened handles to files under
+      # |tempfile_dir|. For example, see crbug.com/865896
+      shutil.rmtree(temp_dir, ignore_errors=True)
 
   print_duration('executing benchmark %s' % command_generator.benchmark, start)
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b73b2cdf..fcd3415 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -111,6 +111,21 @@
             ]
         }
     ],
+    "AndroidClipboardSuggestionContentHidden": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ClipboardSuggestionContentHidden"
+                    ]
+                }
+            ]
+        }
+    ],
     "AndroidDarkSearch": [
         {
             "platforms": [
@@ -4625,6 +4640,26 @@
             ]
         }
     ],
+    "MinimizeAudioProcessingForUnusedOutput": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "MinimizeAudioProcessingForUnusedOutput"
+                    ]
+                }
+            ]
+        }
+    ],
     "MixedFormInterstitial": [
         {
             "platforms": [
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 98bfdbe..9d5338a 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -937,5 +937,12 @@
 const base::Feature kFledgeInterestGroupAPI{"FledgeInterestGroupAPI",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable the ability to minimize processing in the WebRTC APM when all audio
+// tracks are disabled. If disabled, the APM in WebRTC will ignore attempts to
+// set it in a low-processing mode when all audio tracks are disabled.
+const base::Feature kMinimizeAudioProcessingForUnusedOutput{
+    "MinimizeAudioProcessingForUnusedOutput",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 7f0a913a..f0819106 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -390,6 +390,11 @@
 BLINK_COMMON_EXPORT extern const base::Feature kFledgeInterestGroups;
 BLINK_COMMON_EXPORT extern const base::Feature kFledgeInterestGroupAPI;
 
+// Control switch for minimizing processing in the WebRTC APM when all audio
+// tracks are disabled.
+BLINK_COMMON_EXPORT extern const base::Feature
+    kMinimizeAudioProcessingForUnusedOutput;
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 6a1be66..336b1a12 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3209,6 +3209,8 @@
   kHandwritingRecognitionStartDrawing = 3894,
   kHandwritingRecognitionGetPrediction = 3895,
   kWebBluetoothManufacturerDataFilter = 3896,
+  kSanitizerAPIGetConfig = 3897,
+  kSanitizerAPIGetDefaultConfig = 3898,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index 9ef6d26..928150eb 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -60,7 +60,6 @@
 class LayerTreeHost;
 class LayerTreeSettings;
 class TaskGraphRunner;
-class UkmRecorderFactory;
 }
 
 namespace ui {
@@ -93,7 +92,6 @@
       scheduler::WebAgentGroupScheduler& agent_group_scheduler,
       cc::TaskGraphRunner* task_graph_runner,
       const ScreenInfos& screen_info,
-      std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
       const cc::LayerTreeSettings* settings,
       gfx::RenderingPipeline* main_thread_pipeline,
       gfx::RenderingPipeline* compositor_thread_pipeline) = 0;
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index bbc69eb4..593e2e1 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4109,10 +4109,14 @@
           HasEventListeners(event_type_names::kFocusout));
 }
 
-bool Element::IsFocusable() const {
+bool Element::IsBaseElementFocusable() const {
   return Element::IsMouseFocusable() || Element::IsKeyboardFocusable();
 }
 
+bool Element::IsFocusable() const {
+  return IsMouseFocusable() || IsKeyboardFocusable();
+}
+
 bool Element::IsFocusableStyleAfterUpdate() const {
   // In order to check focusable style, we use the existence of LayoutObjects
   // as a proxy for determining whether the element would have a display mode
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 8ede4dce..d523876 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -651,6 +651,9 @@
   bool IsFocusable() const;
   virtual bool IsKeyboardFocusable() const;
   virtual bool IsMouseFocusable() const;
+  // IsBaseElementFocusable() is used by some subclasses to check if the base
+  // element is focusable. This is used to avoid infinite recursion.
+  bool IsBaseElementFocusable() const;
   bool IsFocusedElementInDocument() const;
   Element* AdjustedFocusedElementInTreeScope() const;
   bool IsAutofocusable() const;
diff --git a/third_party/blink/renderer/core/dom/shadow_root.idl b/third_party/blink/renderer/core/dom/shadow_root.idl
index 5c3fcc2..54d8113 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.idl
+++ b/third_party/blink/renderer/core/dom/shadow_root.idl
@@ -34,7 +34,7 @@
     // https://crbug.com/1058762 has been fixed.
     [CEReactions, RaisesException=Setter] attribute [TreatNullAs=EmptyString, StringContext=TrustedHTML] DOMString innerHTML;
     readonly attribute boolean delegatesFocus;
-    [RuntimeEnabled=ManualSlotting] readonly attribute SlotAssignmentMode slotAssignment;
+    readonly attribute SlotAssignmentMode slotAssignment;
 
     // Declarative Shadow DOM getInnerHTML() function.
     [Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, MeasureAs=ElementGetInnerHTML] HTMLString getInnerHTML(optional GetInnerHTMLOptions options = {});
diff --git a/third_party/blink/renderer/core/dom/shadow_root_init.idl b/third_party/blink/renderer/core/dom/shadow_root_init.idl
index e1b435dc..e13bd34b 100644
--- a/third_party/blink/renderer/core/dom/shadow_root_init.idl
+++ b/third_party/blink/renderer/core/dom/shadow_root_init.idl
@@ -10,5 +10,5 @@
 dictionary ShadowRootInit {
     required ShadowRootMode mode;
     boolean delegatesFocus;
-    [RuntimeEnabled=ManualSlotting] SlotAssignmentMode slotAssignment;
+    SlotAssignmentMode slotAssignment;
 };
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 27ab91e4..b35af1dd18 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -450,7 +450,6 @@
     scheduler::WebAgentGroupScheduler& agent_group_scheduler,
     cc::TaskGraphRunner* task_graph_runner,
     const ScreenInfos& screen_infos,
-    std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
     const cc::LayerTreeSettings* settings,
     gfx::RenderingPipeline* main_thread_pipeline,
     gfx::RenderingPipeline* compositor_thread_pipeline) {
@@ -458,8 +457,7 @@
   // much work here.
   widget_base_->InitializeCompositing(
       agent_group_scheduler, task_graph_runner,
-      /*for_child_local_root_frame=*/false, screen_infos,
-      std::move(ukm_recorder_factory), settings,
+      /*for_child_local_root_frame=*/false, screen_infos, settings,
       /*frame_widget_input_handler=*/nullptr, main_thread_pipeline,
       compositor_thread_pipeline);
   cc::LayerTreeDebugState debug_state =
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.h b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
index 9dd78b4..55e3cd9 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.h
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
@@ -160,7 +160,6 @@
       scheduler::WebAgentGroupScheduler& agent_group_scheduler,
       cc::TaskGraphRunner* task_graph_runner,
       const ScreenInfos& screen_infos,
-      std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
       const cc::LayerTreeSettings* settings,
       gfx::RenderingPipeline* main_thread_pipeline,
       gfx::RenderingPipeline* compositor_thread_pipeline) override;
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.cc b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
index a1a96be..01773ff2 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.cc
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
@@ -559,8 +559,7 @@
   ScreenInfos initial_screen_infos(frame_widget->GetInitialScreenInfo());
   frame_widget->InitializeCompositing(
       frame_widget->GetAgentGroupScheduler(), frame_widget->task_graph_runner(),
-      initial_screen_infos, std::make_unique<cc::TestUkmRecorderFactory>(),
-      &layer_tree_settings,
+      initial_screen_infos, &layer_tree_settings,
       /*main_thread_pipeline=*/nullptr,
       /*compositor_thread_pipeline=*/nullptr);
   frame_widget->SetCompositorVisible(true);
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 44f26ec..4491640 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -1920,7 +1920,6 @@
     scheduler::WebAgentGroupScheduler& agent_group_scheduler,
     cc::TaskGraphRunner* task_graph_runner,
     const ScreenInfos& screen_infos,
-    std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
     const cc::LayerTreeSettings* settings,
     gfx::RenderingPipeline* main_thread_pipeline,
     gfx::RenderingPipeline* compositor_thread_pipeline) {
@@ -1928,9 +1927,8 @@
   DCHECK(!non_composited_client_);  // Assure only one initialize is called.
   widget_base_->InitializeCompositing(
       agent_group_scheduler, task_graph_runner, is_for_child_local_root_,
-      screen_infos, std::move(ukm_recorder_factory), settings,
-      input_handler_weak_ptr_factory_.GetWeakPtr(), main_thread_pipeline,
-      compositor_thread_pipeline);
+      screen_infos, settings, input_handler_weak_ptr_factory_.GetWeakPtr(),
+      main_thread_pipeline, compositor_thread_pipeline);
 
   LocalFrameView* frame_view;
   if (is_for_child_local_root_) {
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index b6a95e4..b97a37a 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -338,7 +338,6 @@
       scheduler::WebAgentGroupScheduler& agent_group_scheduler,
       cc::TaskGraphRunner* task_graph_runner,
       const ScreenInfos& screen_infos,
-      std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
       const cc::LayerTreeSettings* settings,
       gfx::RenderingPipeline* main_thread_pipeline,
       gfx::RenderingPipeline* compositor_thread_pipeline) override;
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
index 3d401e9..604bc95 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -264,7 +264,7 @@
     return HTMLElement::IsKeyboardFocusable();
 
   // Skip tabIndex check in a parent class.
-  return IsFocusable();
+  return IsBaseElementFocusable();
 }
 
 bool HTMLFormControlElement::MayTriggerVirtualKeyboard() const {
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
index 0d857bc9..89d0fcc 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -294,7 +294,7 @@
 bool HTMLTextAreaElement::IsKeyboardFocusable() const {
   // If a given text area can be focused at all, then it will always be keyboard
   // focusable.
-  return IsFocusable();
+  return IsBaseElementFocusable();
 }
 
 bool HTMLTextAreaElement::MayTriggerVirtualKeyboard() const {
diff --git a/third_party/blink/renderer/core/html/forms/input_type.cc b/third_party/blink/renderer/core/html/forms/input_type.cc
index 781299f..40f2e45 100644
--- a/third_party/blink/renderer/core/html/forms/input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/input_type.cc
@@ -506,7 +506,7 @@
 }
 
 bool InputType::IsKeyboardFocusable() const {
-  return GetElement().IsFocusable();
+  return GetElement().IsBaseElementFocusable();
 }
 
 bool InputType::MayTriggerVirtualKeyboard() const {
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index 2bd3386..459928b 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -130,6 +130,8 @@
 }
 
 bool HTMLAnchorElement::IsMouseFocusable() const {
+  if (!IsFocusableStyleAfterUpdate())
+    return false;
   if (IsLink())
     return SupportsFocus();
 
@@ -137,9 +139,11 @@
 }
 
 bool HTMLAnchorElement::IsKeyboardFocusable() const {
-  DCHECK(GetDocument().IsActive());
+  if (!IsFocusableStyleAfterUpdate())
+    return false;
 
-  if (IsFocusable() && Element::SupportsFocus())
+  // Anchor is focusable if the base element supports focus and is focusable.
+  if (IsBaseElementFocusable() && Element::SupportsFocus())
     return HTMLElement::IsKeyboardFocusable();
 
   if (IsLink() && !GetDocument().GetPage()->GetChromeClient().TabsToLinks())
diff --git a/third_party/blink/renderer/core/html/html_area_element.cc b/third_party/blink/renderer/core/html/html_area_element.cc
index 58cc6586c..cd0ca59 100644
--- a/third_party/blink/renderer/core/html/html_area_element.cc
+++ b/third_party/blink/renderer/core/html/html_area_element.cc
@@ -184,11 +184,11 @@
 }
 
 bool HTMLAreaElement::IsKeyboardFocusable() const {
-  return IsFocusable();
+  return IsBaseElementFocusable();
 }
 
 bool HTMLAreaElement::IsMouseFocusable() const {
-  return IsFocusable();
+  return IsBaseElementFocusable();
 }
 
 bool HTMLAreaElement::IsFocusableStyle() const {
diff --git a/third_party/blink/renderer/core/html/html_slot_element.idl b/third_party/blink/renderer/core/html/html_slot_element.idl
index 69984e5..c9a8d09e 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.idl
+++ b/third_party/blink/renderer/core/html/html_slot_element.idl
@@ -32,5 +32,5 @@
     [CEReactions, Reflect] attribute DOMString name;
     [ImplementedAs=AssignedNodesForBinding] sequence<Node> assignedNodes(optional AssignedNodesOptions options = {});
     [ImplementedAs=AssignedElementsForBinding] sequence<Element> assignedElements(optional AssignedNodesOptions options = {});
-    [RuntimeEnabled=ManualSlotting, RaisesException] void assign(Node... nodes);
+    [RaisesException] void assign(Node... nodes);
 };
diff --git a/third_party/blink/renderer/core/html/parser/background_html_parser.cc b/third_party/blink/renderer/core/html/parser/background_html_parser.cc
index 922d0e02..ddb2eff 100644
--- a/third_party/blink/renderer/core/html/parser/background_html_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/background_html_parser.cc
@@ -227,6 +227,7 @@
         simulated_token == HTMLTreeBuilderSimulator::kStyleEnd ||
         simulated_token == HTMLTreeBuilderSimulator::kLink ||
         simulated_token == HTMLTreeBuilderSimulator::kCustomElementBegin ||
+        simulated_token == HTMLTreeBuilderSimulator::kDeclarativeShadowDOMEnd ||
         pending_tokens_.size() >= kPendingTokenLimit) {
       EnqueueTokenizedChunk();
 
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.cc b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.cc
index b0f1fa8..a1e7cd7 100644
--- a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.cc
+++ b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.cc
@@ -179,6 +179,17 @@
         }
       } else if (ThreadSafeMatch(tag_name, html_names::kLinkTag)) {
         simulated_token = kLink;
+
+      } else if (ThreadSafeMatch(tag_name, html_names::kTemplateTag)) {
+        TemplateType template_type = TemplateType::kRegular;
+        if (auto* item = token.GetAttributeItem(html_names::kShadowrootAttr)) {
+          String shadow_mode = item->Value();
+          if (EqualIgnoringASCIICase(shadow_mode, "open") ||
+              EqualIgnoringASCIICase(shadow_mode, "closed")) {
+            template_type = TemplateType::kShadow;
+          }
+        }
+        template_stack_.push_back(template_type);
       } else if (!in_select_insertion_mode_) {
         // If we're in the "in select" insertion mode, all of these tags are
         // ignored, so we shouldn't change the tokenizer state:
@@ -239,6 +250,15 @@
       in_select_insertion_mode_ = false;
     if (ThreadSafeMatch(tag_name, html_names::kStyleTag))
       simulated_token = kStyleEnd;
+
+    if (ThreadSafeMatch(tag_name, html_names::kTemplateTag)) {
+      if (!template_stack_.IsEmpty()) {
+        TemplateType type = std::move(template_stack_.back());
+        template_stack_.pop_back();
+        if (type == TemplateType::kShadow)
+          simulated_token = kDeclarativeShadowDOMEnd;
+      }
+    }
   }
   if (token.GetType() == HTMLToken::kStartTag &&
       simulated_token == kOtherToken) {
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.h b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.h
index 7a9922ca..184f58f 100644
--- a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.h
+++ b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator.h
@@ -42,6 +42,8 @@
  private:
   enum Namespace { HTML, SVG, kMathML };
 
+  enum class TemplateType { kRegular, kShadow };
+
  public:
   enum SimulatedToken {
     kValidScriptStart,
@@ -49,6 +51,7 @@
     kLink,
     kStyleEnd,
     kCustomElementBegin,
+    kDeclarativeShadowDOMEnd,
     kOtherToken
   };
 
@@ -71,6 +74,10 @@
   HTMLParserOptions options_;
   State namespace_stack_;
   bool in_select_insertion_mode_;
+
+  // TODO(crbug.com/901056, masonfreed) Remove all of this template tracking
+  // code once the synchronous HTML parser lands.
+  Vector<TemplateType, 1> template_stack_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 85eda5a1..7b6fcb0 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -7706,6 +7706,10 @@
 }
 
 void LayoutBox::CheckIsVisualOverflowComputed() const {
+  // TODO(crbug.com/1205708): There are still too many failures. Disable the
+  // the check for now. Need to investigate the reason.
+  return;
+  /*
   if (NGInkOverflow::ReadUnsetAsNoneScope::IsActive())
     return;
   if (!CanUseFragmentsForVisualOverflow())
@@ -7715,6 +7719,7 @@
     return;
   for (const NGPhysicalBoxFragment& fragment : PhysicalFragments())
     DCHECK(fragment.IsInkOverflowComputed());
+  */
 }
 #endif
 
diff --git a/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc b/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
index c2e512d0..4e0f6e7 100644
--- a/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
+++ b/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
@@ -117,17 +117,22 @@
           document, node.Style(), definition_->native_invalidation_properties_,
           definition_->custom_invalidation_properties_);
 
-  ScriptValue return_value;
-  if (!definition_->layout_
-           ->Invoke(instance_.NewLocal(isolate), children, edges, constraints,
-                    style_map)
-           .To(&return_value))
-    return false;
-
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
   v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(execution_context);
   DCHECK(microtask_queue);
 
+  ScriptValue return_value;
+  {
+    v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
+                                         v8::MicrotasksScope::kRunMicrotasks);
+    if (!definition_->layout_
+             ->Invoke(instance_.NewLocal(isolate), children, edges, constraints,
+                      style_map)
+             .To(&return_value)) {
+      return false;
+    }
+  }
+
   ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
                                  "CSSLayoutAPI", "Layout");
 
@@ -144,16 +149,17 @@
   // Run the work queue until exhaustion.
   auto& queue = *custom_layout_scope->Queue();
   while (!queue.IsEmpty()) {
-    // The queue may mutate (re-allocating the vector) while running a task.
-    for (wtf_size_t index = 0; index < queue.size(); ++index) {
-      auto task = queue[index];
-      task->Run(space, node.Style(), border_box_size.block_size);
-    }
-    queue.clear();
     {
-      v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
-                                           v8::MicrotasksScope::kRunMicrotasks);
+      v8::MicrotasksScope microtasks_scope(
+          isolate, microtask_queue, v8::MicrotasksScope::kDoNotRunMicrotasks);
+      // The queue may mutate (re-allocating the vector) while running a task.
+      for (wtf_size_t index = 0; index < queue.size(); ++index) {
+        auto task = queue[index];
+        task->Run(space, node.Style(), border_box_size.block_size);
+      }
+      queue.clear();
     }
+    microtask_queue->PerformCheckpoint(isolate);
   }
 
   if (exception_state.HadException()) {
@@ -193,6 +199,8 @@
   // Serialize any extra data provided by the web-developer to potentially pass
   // up to the parent custom layout.
   if (fragment_result_options->hasData()) {
+    v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
+                                         v8::MicrotasksScope::kRunMicrotasks);
     // We serialize "kForStorage" so that SharedArrayBuffers can't be shared
     // between LayoutWorkletGlobalScopes.
     *fragment_result_data = SerializedScriptValue::Serialize(
@@ -247,16 +255,21 @@
           document, node.Style(), definition_->native_invalidation_properties_,
           definition_->custom_invalidation_properties_);
 
-  ScriptValue return_value;
-  if (!definition_->intrinsic_sizes_
-           ->Invoke(instance_.NewLocal(isolate), children, edges, style_map)
-           .To(&return_value))
-    return false;
-
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
   v8::MicrotaskQueue* microtask_queue = ToMicrotaskQueue(execution_context);
   DCHECK(microtask_queue);
 
+  ScriptValue return_value;
+  {
+    v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
+                                         v8::MicrotasksScope::kRunMicrotasks);
+    if (!definition_->intrinsic_sizes_
+             ->Invoke(instance_.NewLocal(isolate), children, edges, style_map)
+             .To(&return_value)) {
+      return false;
+    }
+  }
+
   ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
                                  "CSSLayoutAPI", "IntrinsicSizes");
 
@@ -273,17 +286,18 @@
   // Run the work queue until exhaustion.
   auto& queue = *custom_layout_scope->Queue();
   while (!queue.IsEmpty()) {
-    // The queue may mutate (re-allocating the vector) while running a task.
-    for (wtf_size_t index = 0; index < queue.size(); ++index) {
-      auto task = queue[index];
-      task->Run(space, node.Style(), child_available_block_size,
-                child_depends_on_block_constraints);
-    }
-    queue.clear();
     {
-      v8::MicrotasksScope microtasks_scope(isolate, microtask_queue,
-                                           v8::MicrotasksScope::kRunMicrotasks);
+      v8::MicrotasksScope microtasks_scope(
+          isolate, microtask_queue, v8::MicrotasksScope::kDoNotRunMicrotasks);
+      // The queue may mutate (re-allocating the vector) while running a task.
+      for (wtf_size_t index = 0; index < queue.size(); ++index) {
+        auto task = queue[index];
+        task->Run(space, node.Style(), child_available_block_size,
+                  child_depends_on_block_constraints);
+      }
+      queue.clear();
     }
+    microtask_queue->PerformCheckpoint(isolate);
   }
 
   if (exception_state.HadException()) {
diff --git a/third_party/blink/renderer/core/svg/svg_a_element.cc b/third_party/blink/renderer/core/svg/svg_a_element.cc
index 9d456a17..baa73e5 100644
--- a/third_party/blink/renderer/core/svg/svg_a_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_a_element.cc
@@ -196,7 +196,7 @@
 }
 
 bool SVGAElement::IsKeyboardFocusable() const {
-  if (IsFocusable() && Element::SupportsFocus())
+  if (IsBaseElementFocusable() && Element::SupportsFocus())
     return SVGElement::IsKeyboardFocusable();
   if (IsLink() && !GetDocument().GetPage()->GetChromeClient().TabsToLinks())
     return false;
diff --git a/third_party/blink/renderer/core/svg/svg_svg_element.cc b/third_party/blink/renderer/core/svg/svg_svg_element.cc
index edfedd2..ba5f5be 100644
--- a/third_party/blink/renderer/core/svg/svg_svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_svg_element.cc
@@ -651,7 +651,7 @@
   if (width_attr.IsPercentage())
     return base::nullopt;
   SVGLengthContext length_context(this);
-  return width_attr.Value(length_context);
+  return std::max(0.0f, width_attr.Value(length_context));
 }
 
 base::Optional<float> SVGSVGElement::IntrinsicHeight() const {
@@ -662,7 +662,7 @@
   if (height_attr.IsPercentage())
     return base::nullopt;
   SVGLengthContext length_context(this);
-  return height_attr.Value(length_context);
+  return std::max(0.0f, height_attr.Value(length_context));
 }
 
 AffineTransform SVGSVGElement::ViewBoxToViewTransform(
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
index 3880df1..9cd712a 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
@@ -30,6 +30,8 @@
 #include "media/base/limits.h"
 #include "media/webrtc/helpers.h"
 #include "media/webrtc/webrtc_switches.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.h"
 #include "third_party/blink/renderer/platform/mediastream/aec_dump_agent_impl.h"
@@ -66,7 +68,7 @@
 
 bool Allow48kHzApmProcessing() {
   return base::FeatureList::IsEnabled(
-      features::kWebRtcAllow48kHzProcessingOnArm);
+      ::features::kWebRtcAllow48kHzProcessingOnArm);
 }
 
 constexpr int kBuffersPerSecond = 100;  // 10 ms per buffer.
@@ -250,6 +252,9 @@
   DCHECK(main_thread_runner_);
   DETACH_FROM_THREAD(capture_thread_checker_);
   DETACH_FROM_THREAD(render_thread_checker_);
+  SendLogMessage(
+      String::Format("%s({use_capture_multi_channel_processing=%s})", __func__,
+                     use_capture_multi_channel_processing ? "true" : "false"));
 
   InitializeAudioProcessingModule(properties);
 }
@@ -356,6 +361,17 @@
   return output_format_;
 }
 
+void MediaStreamAudioProcessor::SetOutputWillBeMuted(bool muted) {
+  DCHECK(main_thread_runner_->BelongsToCurrentThread());
+  DCHECK(base::FeatureList::IsEnabled(
+      features::kMinimizeAudioProcessingForUnusedOutput));
+  SendLogMessage(
+      String::Format("%s({muted=%s})", __func__, muted ? "true" : "false"));
+  if (audio_processing_) {
+    audio_processing_->set_output_will_be_muted(muted);
+  }
+}
+
 void MediaStreamAudioProcessor::OnStartDump(base::File dump_file) {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
 
@@ -493,6 +509,7 @@
     const blink::AudioProcessingProperties& properties) {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
   DCHECK(!audio_processing_);
+  SendLogMessage(String::Format("%s()", __func__));
 
   // Note: The audio mirroring constraint (i.e., swap left and right channels)
   // is handled within this MediaStreamAudioProcessor and does not, by itself,
@@ -536,7 +553,7 @@
     auto* experimental_agc =
         new webrtc::ExperimentalAgc(true, startup_min_volume.value_or(0));
     experimental_agc->digital_adaptive_disabled =
-        base::FeatureList::IsEnabled(features::kWebRtcHybridAgc);
+        base::FeatureList::IsEnabled(::features::kWebRtcHybridAgc);
 
     config.Set<webrtc::ExperimentalAgc>(experimental_agc);
 #if BUILDFLAG(IS_CHROMECAST)
@@ -584,42 +601,42 @@
       properties.goog_experimental_auto_gain_control) {
     base::Optional<blink::AdaptiveGainController2Properties> agc2_properties;
     if (properties.goog_experimental_auto_gain_control &&
-        base::FeatureList::IsEnabled(features::kWebRtcHybridAgc)) {
+        base::FeatureList::IsEnabled(::features::kWebRtcHybridAgc)) {
       DCHECK(properties.goog_auto_gain_control)
           << "Cannot enable hybrid AGC when AGC is disabled.";
       agc2_properties = blink::AdaptiveGainController2Properties{};
       agc2_properties->vad_probability_attack =
           base::GetFieldTrialParamByFeatureAsDouble(
-              features::kWebRtcHybridAgc, "vad_probability_attack", 0.3);
+              ::features::kWebRtcHybridAgc, "vad_probability_attack", 0.3);
       agc2_properties->use_peaks_not_rms =
-          base::GetFieldTrialParamByFeatureAsBool(features::kWebRtcHybridAgc,
+          base::GetFieldTrialParamByFeatureAsBool(::features::kWebRtcHybridAgc,
                                                   "use_peaks_not_rms", false);
       agc2_properties->level_estimator_speech_frames_threshold =
           base::GetFieldTrialParamByFeatureAsInt(
-              features::kWebRtcHybridAgc,
+              ::features::kWebRtcHybridAgc,
               "level_estimator_speech_frames_threshold", 6);
       agc2_properties->initial_saturation_margin_db =
           base::GetFieldTrialParamByFeatureAsInt(
-              features::kWebRtcHybridAgc, "initial_saturation_margin", 20);
+              ::features::kWebRtcHybridAgc, "initial_saturation_margin", 20);
       agc2_properties->extra_saturation_margin_db =
-          base::GetFieldTrialParamByFeatureAsInt(features::kWebRtcHybridAgc,
+          base::GetFieldTrialParamByFeatureAsInt(::features::kWebRtcHybridAgc,
                                                  "extra_saturation_margin", 5);
       agc2_properties->gain_applier_speech_frames_threshold =
           base::GetFieldTrialParamByFeatureAsInt(
-              features::kWebRtcHybridAgc,
+              ::features::kWebRtcHybridAgc,
               "gain_applier_speech_frames_threshold", 6);
       agc2_properties->max_gain_change_db_per_second =
           base::GetFieldTrialParamByFeatureAsInt(
-              features::kWebRtcHybridAgc, "max_gain_change_db_per_second", 3);
+              ::features::kWebRtcHybridAgc, "max_gain_change_db_per_second", 3);
       agc2_properties->max_output_noise_level_dbfs =
           base::GetFieldTrialParamByFeatureAsInt(
-              features::kWebRtcHybridAgc, "max_output_noise_level_dbfs", -55);
+              ::features::kWebRtcHybridAgc, "max_output_noise_level_dbfs", -55);
       agc2_properties->sse2_allowed = base::GetFieldTrialParamByFeatureAsBool(
-          features::kWebRtcHybridAgc, "sse2_allowed", true);
+          ::features::kWebRtcHybridAgc, "sse2_allowed", true);
       agc2_properties->avx2_allowed = base::GetFieldTrialParamByFeatureAsBool(
-          features::kWebRtcHybridAgc, "avx2_allowed", true);
+          ::features::kWebRtcHybridAgc, "avx2_allowed", true);
       agc2_properties->neon_allowed = base::GetFieldTrialParamByFeatureAsBool(
-          features::kWebRtcHybridAgc, "neon_allowed", true);
+          ::features::kWebRtcHybridAgc, "neon_allowed", true);
     }
     blink::ConfigAutomaticGainControl(
         properties.goog_auto_gain_control,
@@ -648,6 +665,9 @@
     const media::AudioParameters& input_format) {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
   DCHECK(input_format.IsValid());
+  SendLogMessage(String::Format("%s({input_format=[%s]})", __func__,
+                                input_format.AsHumanReadableString().c_str()));
+
   input_format_ = input_format;
 
   // TODO(crbug/881275): For now, we assume fixed parameters for the output when
@@ -728,6 +748,12 @@
     // Explicitly set number of channels for discrete channel layouts.
     output_format_.set_channels_for_discrete(input_format.channels());
   }
+  SendLogMessage(
+      String::Format("%s => (output_format=[%s])", __func__,
+                     output_format_.AsHumanReadableString().c_str()));
+  SendLogMessage(
+      String::Format("%s => (FIFO: processing_frames=%d, output_channels=%d)",
+                     __func__, processing_frames, fifo_output_channels));
 
   capture_fifo_ = std::make_unique<MediaStreamAudioFifo>(
       input_format.channels(), fifo_output_channels,
@@ -839,4 +865,11 @@
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
 }
 
+void MediaStreamAudioProcessor::SendLogMessage(const WTF::String& message) {
+  WebRtcLogMessage(String::Format("MSAP::%s [this=0x%" PRIXPTR "]",
+                                  message.Utf8().c_str(),
+                                  reinterpret_cast<uintptr_t>(this))
+                       .Utf8());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
index 5e2841d..6bd9547 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/platform/mediastream/aec_dump_agent_impl.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h"
 #include "third_party/blink/renderer/platform/webrtc/webrtc_source.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/webrtc/api/media_stream_interface.h"
 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
 #include "third_party/webrtc/rtc_base/task_queue.h"
@@ -111,6 +112,11 @@
   // Accessor to check if the audio processing is enabled or not.
   bool has_audio_processing() const { return !!audio_processing_; }
 
+  // Instructs the Audio Processing Module (APM) to reduce its complexity when
+  // |muted| is true. This mode is triggered when all audio tracks are disabled.
+  // The default APM complexity mode is restored by |muted| set to false.
+  void SetOutputWillBeMuted(bool muted);
+
   // AecDumpAgentImpl::Delegate implementation.
   // Called on the main render thread.
   void OnStartDump(base::File dump_file) override;
@@ -181,6 +187,8 @@
   // Update AEC stats. Called on the main render thread.
   void UpdateAecStats();
 
+  void SendLogMessage(const WTF::String& message);
+
   // Cached value for the render delay latency. This member is accessed by
   // both the capture audio thread and the render audio thread.
   base::subtle::Atomic32 render_delay_ms_;
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index c7c58e4..e1334f7 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -49,10 +49,12 @@
 #include "third_party/blink/renderer/modules/mediastream/media_stream_utils.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
 #include "third_party/blink/renderer/modules/mediastream/overconstrained_error.h"
+#include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"
 #include "third_party/blink/renderer/modules/mediastream/user_media_controller.h"
 #include "third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -70,10 +72,6 @@
 static const char kContentHintStringVideoDetail[] = "detail";
 static const char kContentHintStringVideoText[] = "text";
 
-void SendLogMessage(const std::string& message) {
-  blink::WebRtcLogMessage("MST::" + message);
-}
-
 // The set of constrainable properties for image capture is available at
 // https://w3c.github.io/mediacapture-image/#constrainable-properties
 // TODO(guidou): Integrate image-capture constraints processing with the
@@ -236,13 +234,14 @@
     : ready_state_(ready_state),
       component_(component),
       execution_context_(context) {
-  SendLogMessage(GetTrackLogString());
   component_->Source()->AddObserver(this);
 
   // If the source is already non-live at this point, the observer won't have
   // been called. Update the muted state manually.
   component_->SetMuted(ready_state_ == MediaStreamSource::kReadyStateMuted);
 
+  SendLogMessage(String::Format("%s()", __func__));
+
   MediaStreamVideoTrack* const video_track =
       MediaStreamVideoTrack::From(Component());
   if (video_track && component_->Source() &&
@@ -269,16 +268,6 @@
 
 MediaStreamTrack::~MediaStreamTrack() = default;
 
-std::string MediaStreamTrack::GetTrackLogString() const {
-  String str = String::Format(
-      "MediaStreamTrack([kind: %s, id: %s, label: %s, enabled: %s, muted: %s, "
-      "readyState: %s])",
-      kind().Utf8().c_str(), id().Utf8().c_str(), label().Utf8().c_str(),
-      enabled() ? "true" : "false", muted() ? "true" : "false",
-      readyState().Utf8().c_str());
-  return str.Utf8();
-}
-
 String MediaStreamTrack::kind() const {
   DEFINE_STATIC_LOCAL(String, audio_kind, ("audio"));
   DEFINE_STATIC_LOCAL(String, video_kind, ("video"));
@@ -307,16 +296,37 @@
 }
 
 void MediaStreamTrack::setEnabled(bool enabled) {
-  SendLogMessage(base::StringPrintf("setEnabled([id=%s] {enabled=%s})",
-                                    id().Utf8().c_str(),
-                                    enabled ? "true" : "false"));
   if (enabled == component_->Enabled())
     return;
 
   component_->SetEnabled(enabled);
 
-  if (!Ended())
-    DidSetMediaStreamTrackEnabled(component_.Get());
+  SendLogMessage(
+      String::Format("%s({enabled=%s})", __func__, enabled ? "true" : "false"));
+
+  if (Ended())
+    return;
+
+  DidSetMediaStreamTrackEnabled(component_.Get());
+
+  MediaStreamAudioSource* media_stream_audio_source =
+      MediaStreamAudioSource::From(component_->Source());
+  ProcessedLocalAudioSource* processed_local_audio_source =
+      ProcessedLocalAudioSource::From(media_stream_audio_source);
+  if (media_stream_audio_source && processed_local_audio_source) {
+    if (!enabled) {
+      // One track was disabled. Check if all tracks are disabled and inform the
+      // APM about the state. The APM can enter a low-complexity mode if it
+      // knows that all tracks are muted and that saves CPU cycles.
+      const bool all_tracks_disabled =
+          media_stream_audio_source->AllTracksAreDisabled();
+      processed_local_audio_source->SetOutputWillBeMuted(all_tracks_disabled);
+    } else {
+      // At least one track is enabled. Tell the APM to go back to its normal
+      // mode.
+      processed_local_audio_source->SetOutputWillBeMuted(false);
+    }
+  }
 }
 
 bool MediaStreamTrack::muted() const {
@@ -345,8 +355,8 @@
 }
 
 void MediaStreamTrack::SetContentHint(const String& hint) {
-  SendLogMessage(base::StringPrintf("SetContentHint([id=%s] {hint=%s})",
-                                    id().Utf8().c_str(), hint.Utf8().c_str()));
+  SendLogMessage(
+      String::Format("%s({hint=%s})", __func__, hint.Utf8().c_str()));
   WebMediaStreamTrack::ContentHintType translated_hint =
       WebMediaStreamTrack::ContentHintType::kNone;
   switch (component_->Source()->GetType()) {
@@ -407,6 +417,8 @@
   if (ready_state_ != MediaStreamSource::kReadyStateEnded &&
       ready_state_ != ready_state) {
     ready_state_ = ready_state;
+    SendLogMessage(String::Format("%s({ready_state=%s})", __func__,
+                                  readyState().Utf8().c_str()));
 
     // Observers may dispatch events which create and add new Observers;
     // take a snapshot so as to safely iterate.
@@ -418,7 +430,7 @@
 }
 
 void MediaStreamTrack::stopTrack(ExecutionContext* execution_context) {
-  SendLogMessage(base::StringPrintf("stopTrack([id=%s])", id().Utf8().c_str()));
+  SendLogMessage(String::Format("%s()", __func__));
   if (Ended())
     return;
 
@@ -433,6 +445,7 @@
 }
 
 MediaStreamTrack* MediaStreamTrack::clone(ScriptState* script_state) {
+  SendLogMessage(String::Format("%s()", __func__));
   MediaStreamComponent* cloned_component = Component()->Clone();
   MediaStreamTrack* cloned_track = MakeGarbageCollected<MediaStreamTrack>(
       ExecutionContext::From(script_state), cloned_component, ready_state_,
@@ -776,9 +789,7 @@
       feature_handle_for_scheduler_.reset();
       break;
   }
-  SendLogMessage(
-      base::StringPrintf("SourceChangedState([id=%s] {readyState=%s})",
-                         id().Utf8().c_str(), readyState().Utf8().c_str()));
+  SendLogMessage(String::Format("%s()", __func__));
 }
 
 void MediaStreamTrack::PropagateTrackEnded() {
@@ -866,4 +877,16 @@
   observers_.insert(observer);
 }
 
+void MediaStreamTrack::SendLogMessage(const WTF::String& message) {
+  WebRtcLogMessage(
+      String::Format(
+          "MST::%s [kind: %s, id: %s, label: %s, enabled: %s, muted: %s, "
+          "readyState: %s, remote=%s]",
+          message.Utf8().c_str(), kind().Utf8().c_str(), id().Utf8().c_str(),
+          label().Utf8().c_str(), enabled() ? "true" : "false",
+          muted() ? "true" : "false", readyState().Utf8().c_str(),
+          component_->Source()->Remote() ? "true" : "false")
+          .Utf8());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.h b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
index 72e6d4f..6bfaff93 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
@@ -35,6 +35,7 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
@@ -130,7 +131,7 @@
   void applyConstraintsImageCapture(ScriptPromiseResolver*,
                                     const MediaTrackConstraints*);
 
-  std::string GetTrackLogString() const;
+  void SendLogMessage(const WTF::String& message);
 
   // Ensures that |feature_handle_for_scheduler_| is initialized.
   void EnsureFeatureHandleForScheduler();
diff --git a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.cc b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.cc
index ce1b3947..7f66df7 100644
--- a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.cc
+++ b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
@@ -19,6 +20,7 @@
 #include "media/base/sample_rates.h"
 #include "media/webrtc/audio_processor_controls.h"
 #include "media/webrtc/webrtc_switches.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -95,7 +97,8 @@
       bool_to_string(properties.goog_experimental_noise_suppression),
       bool_to_string(properties.goog_highpass_filter),
       bool_to_string(properties.goog_experimental_auto_gain_control),
-      bool_to_string(base::FeatureList::IsEnabled(features::kWebRtcHybridAgc)));
+      bool_to_string(
+          base::FeatureList::IsEnabled(::features::kWebRtcHybridAgc)));
   return str;
 }
 }  // namespace
@@ -344,6 +347,14 @@
   return audio_processor_ && audio_processor_->has_audio_processing();
 }
 
+void ProcessedLocalAudioSource::SetOutputWillBeMuted(bool muted) {
+  if (base::FeatureList::IsEnabled(
+          features::kMinimizeAudioProcessingForUnusedOutput) &&
+      HasAudioProcessing()) {
+    audio_processor_->SetOutputWillBeMuted(muted);
+  }
+}
+
 void ProcessedLocalAudioSource::SetVolume(int volume) {
   DVLOG(1) << "ProcessedLocalAudioSource::SetVolume()";
   DCHECK_LE(volume, MaxVolume());
diff --git a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h
index 2d4fbaa..557d286 100644
--- a/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h
+++ b/third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h
@@ -78,6 +78,11 @@
 
   bool HasAudioProcessing() const;
 
+  // Instructs the Audio Processing Module (APM) to reduce its complexity when
+  // |muted| is true. This mode is triggered when all audio tracks are disabled.
+  // The default APM complexity mode is restored when |muted| is set to false.
+  void SetOutputWillBeMuted(bool muted);
+
   const scoped_refptr<blink::MediaStreamAudioLevelCalculator::Level>&
   audio_level() const {
     return level_calculator_.level();
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
index 5ecd4b1..4847d121 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
@@ -1402,6 +1402,8 @@
   if (blink::IsScreenCaptureMediaType(device.type) ||
       !blink::MediaStreamAudioProcessor::WouldModifyAudio(
           audio_processing_properties)) {
+    SendLogMessage(
+        base::StringPrintf("%s => (no audiprocessing is used)", __func__));
     return std::make_unique<blink::LocalMediaStreamAudioSource>(
         frame_, device,
         base::OptionalOrNullptr(current_request_info_->audio_capture_settings()
@@ -1412,6 +1414,8 @@
 
   // The audio device is not associated with screen capture and also requires
   // processing.
+  SendLogMessage(
+      base::StringPrintf("%s => (audiprocessing is required)", __func__));
   return std::make_unique<blink::ProcessedLocalAudioSource>(
       *frame_, device, stream_controls->disable_local_echo,
       audio_processing_properties,
diff --git a/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn b/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
index 85c11ce6c6..f8c475df 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
+++ b/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
@@ -10,6 +10,8 @@
   sources = [
     "sanitizer.cc",
     "sanitizer.h",
+    "sanitizer_config_impl.cc",
+    "sanitizer_config_impl.h",
   ]
 }
 
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
index 3ca50f9f..3278e9a3 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
@@ -42,87 +42,68 @@
 
 namespace blink {
 
-Sanitizer* Sanitizer::Create(ExecutionContext* execution_context,
-                             const SanitizerConfig* config,
-                             ExceptionState& exception_state) {
-  return MakeGarbageCollected<Sanitizer>(execution_context, config);
+namespace {
+
+bool ConfigIsEmpty(const SanitizerConfig* config) {
+  return !config ||
+         (!config->hasDropElements() && !config->hasBlockElements() &&
+          !config->hasAllowElements() && !config->hasDropAttributes() &&
+          !config->hasAllowAttributes() && !config->hasAllowCustomElements());
 }
 
-Sanitizer::Sanitizer(ExecutionContext* execution_context,
-                     const SanitizerConfig* config)
-    : allow_custom_elements_(config->allowCustomElements()) {
-  bool use_default_config = true;
-  if (config->allowCustomElements()) {
-    use_default_config = false;
-  }
+SanitizerConfig* SanitizerConfigCopy(const SanitizerConfig* config) {
+  if (!config)
+    return nullptr;
 
-  // Format dropElements to uppercase.
-  if (config->hasDropElements()) {
-    ElementFormatter(drop_elements_, config->dropElements());
-    use_default_config = false;
-  }
-
-  // Format blockElements to uppercase.
-  if (config->hasBlockElements()) {
-    ElementFormatter(block_elements_, config->blockElements());
-    use_default_config = false;
-  }
-
-  // Format allowElements to uppercase.
-  if (config->hasAllowElements()) {
-    ElementFormatter(allow_elements_, config->allowElements());
-    use_default_config = false;
-  } else {
-    allow_elements_ = default_allow_elements_;
-  }
-
-  // Format dropAttributes to lowercase.
-  if (config->hasDropAttributes()) {
-    AttrFormatter(drop_attributes_, config->dropAttributes());
-    use_default_config = false;
-  }
-
-  // Format allowAttributes to lowercase.
+  SanitizerConfig* copy = SanitizerConfig::Create();
   if (config->hasAllowAttributes()) {
-    AttrFormatter(allow_attributes_, config->allowAttributes());
-    use_default_config = false;
-  } else {
-    allow_attributes_ = default_allow_attributes_;
+    copy->setAllowAttributes(config->allowAttributes());
+  }
+  if (config->hasAllowCustomElements()) {
+    copy->setAllowCustomElements(config->allowCustomElements());
+  }
+  if (config->hasAllowElements()) {
+    copy->setAllowElements(config->allowElements());
+  }
+  if (config->hasBlockElements()) {
+    copy->setBlockElements(config->blockElements());
+  }
+  if (config->hasDropAttributes()) {
+    copy->setDropAttributes(config->dropAttributes());
+  }
+  if (config->hasDropElements()) {
+    copy->setDropElements(config->dropElements());
+  }
+  return copy;
+}
+
+}  // anonymous namespace
+
+Sanitizer::Sanitizer(ExecutionContext* execution_context,
+                     const SanitizerConfig* config) {
+  // The spec treats an absent config as the default. We'll handle this by
+  // normalizing this here to make sure the config_dictionary_ is nullptr
+  // in these cases, while the config_ will be a copy of the default config.
+  if (ConfigIsEmpty(config)) {
+    config = nullptr;
   }
 
-  if (use_default_config) {
-    // TODO(lyf): Add unit tests for counters.
+  config_ = SanitizerConfigImpl::From(config);
+  config_dictionary_ = SanitizerConfigCopy(config);
+  if (!config_dictionary_) {
     UseCounter::Count(execution_context,
                       WebFeature::kSanitizerAPIDefaultConfiguration);
   }
 }
 
-void Sanitizer::ElementFormatter(HashSet<String>& element_set,
-                                 const Vector<String>& elements) {
-  for (const String& s : elements) {
-    element_set.insert(s.UpperASCII());
-  }
-}
-
-void Sanitizer::AttrFormatter(
-    HashMap<String, Vector<String>>& attr_map,
-    const Vector<std::pair<String, Vector<String>>>& attrs) {
-  for (const std::pair<String, Vector<String>>& pair : attrs) {
-    const String& lower_attr = pair.first.LowerASCII();
-    if (pair.second == kVectorStar || pair.second.Contains("*")) {
-      attr_map.insert(lower_attr, kVectorStar);
-    } else {
-      Vector<String> elements;
-      for (const String& s : pair.second) {
-        elements.push_back(s.UpperASCII());
-      }
-      attr_map.insert(lower_attr, elements);
-    }
-  }
-}
-
 Sanitizer::~Sanitizer() = default;
 
+Sanitizer* Sanitizer::Create(ExecutionContext* execution_context,
+                             const SanitizerConfig* config,
+                             ExceptionState& exception_state) {
+  return MakeGarbageCollected<Sanitizer>(execution_context, config);
+}
+
 String Sanitizer::sanitizeToString(ScriptState* script_state,
                                    StringOrDocumentFragmentOrDocument& input,
                                    ExceptionState& exception_state) {
@@ -229,7 +210,7 @@
       node = DropElement(node, fragment);
       UseCounter::Count(window->GetExecutionContext(),
                         WebFeature::kSanitizerAPIActionTaken);
-    } else if (is_custom_element && !allow_custom_elements_) {
+    } else if (is_custom_element && !config_.allow_custom_elements_) {
       // 4. If |kind| is `custom` and if allow_custom_elements_ is unset or set
       // to anything other than `true`, then 'drop'.
       node = DropElement(node, fragment);
@@ -240,17 +221,17 @@
       node = DropElement(node, fragment);
       UseCounter::Count(window->GetExecutionContext(),
                         WebFeature::kSanitizerAPIActionTaken);
-    } else if (drop_elements_.Contains(name)) {
+    } else if (config_.drop_elements_.Contains(name)) {
       // 5. If |name| is in |config|'s [=element drop list=] then 'drop'.
       node = DropElement(node, fragment);
       UseCounter::Count(window->GetExecutionContext(),
                         WebFeature::kSanitizerAPIActionTaken);
-    } else if (block_elements_.Contains(name)) {
+    } else if (config_.block_elements_.Contains(name)) {
       // 6. If |name| is in |config|'s [=element block list=] then 'block'.
       node = BlockElement(node, fragment, exception_state);
       UseCounter::Count(window->GetExecutionContext(),
                         WebFeature::kSanitizerAPIActionTaken);
-    } else if (!allow_elements_.Contains(name)) {
+    } else if (!config_.allow_elements_.Contains(name)) {
       // 7. if |name| is not in |config|'s [=element allow list=] then 'block'.
       node = BlockElement(node, fragment, exception_state);
       UseCounter::Count(window->GetExecutionContext(),
@@ -325,8 +306,8 @@
                              String& node_name,
                              LocalDOMWindow* window) {
   Element* element = To<Element>(node);
-  if (allow_attributes_.at("*").Contains(node_name)) {
-  } else if (drop_attributes_.at("*").Contains(node_name)) {
+  if (config_.allow_attributes_.at("*").Contains(node_name)) {
+  } else if (config_.drop_attributes_.at("*").Contains(node_name)) {
     for (const auto& name : element->getAttributeNames()) {
       element->removeAttribute(name);
       UseCounter::Count(window->GetExecutionContext(),
@@ -339,12 +320,12 @@
       bool drop = (baseline_drop_attributes_.Contains(name) &&
                    (baseline_drop_attributes_.at(name) == kVectorStar ||
                     baseline_drop_attributes_.at(name).Contains(node_name))) ||
-                  (drop_attributes_.Contains(name) &&
-                   (drop_attributes_.at(name) == kVectorStar ||
-                    drop_attributes_.at(name).Contains(node_name))) ||
-                  !(allow_attributes_.Contains(name) &&
-                    (allow_attributes_.at(name) == kVectorStar ||
-                     allow_attributes_.at(name).Contains(node_name)));
+                  (config_.drop_attributes_.Contains(name) &&
+                   (config_.drop_attributes_.at(name) == kVectorStar ||
+                    config_.drop_attributes_.at(name).Contains(node_name))) ||
+                  !(config_.allow_attributes_.Contains(name) &&
+                    (config_.allow_attributes_.at(name) == kVectorStar ||
+                     config_.allow_attributes_.at(name).Contains(node_name)));
       // 9. If |element|'s [=element interface=] is {{HTMLAnchorElement}} or
       // {{HTMLAreaElement}} and |element|'s `protocol` property is
       // "javascript:", then remove the `href` attribute from |element|.
@@ -384,8 +365,19 @@
   return node;
 }
 
+SanitizerConfig* Sanitizer::config() const {
+  return SanitizerConfigCopy(config_dictionary_
+                                 ? config_dictionary_.Get()
+                                 : SanitizerConfigImpl::defaultConfig());
+}
+
+SanitizerConfig* Sanitizer::defaultConfig() {
+  return SanitizerConfigCopy(SanitizerConfigImpl::defaultConfig());
+}
+
 void Sanitizer::Trace(Visitor* visitor) const {
   ScriptWrappable::Trace(visitor);
+  visitor->Trace(config_dictionary_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.h b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.h
index 748456a..c5414e12 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.h
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -44,6 +45,9 @@
                              StringOrTrustedHTMLOrDocumentFragmentOrDocument&,
                              ExceptionState&);
 
+  SanitizerConfig* config() const;
+  static SanitizerConfig* defaultConfig();
+
   void Trace(Visitor*) const override;
 
  private:
@@ -66,40 +70,15 @@
                                  StringOrDocumentFragmentOrDocument&,
                                  ExceptionState&);
 
-  HashSet<String> allow_elements_ = {};
-  HashSet<String> block_elements_ = {};
-  HashSet<String> drop_elements_ = {};
-  HashMap<String, Vector<String>> allow_attributes_ = {};
-  HashMap<String, Vector<String>> drop_attributes_ = {};
-
-  bool allow_custom_elements_ = false;
+  SanitizerConfigImpl config_;
+  Member<const SanitizerConfig> config_dictionary_;
 
   // TODO(lyf): make it all-oilpan.
+  const Vector<String> kVectorStar = Vector<String>({"*"});
   const HashSet<String> baseline_drop_elements_ = {
       "APPLET",   "BASE",    "EMBED",    "IFRAME", "NOEMBED",
       "NOFRAMES", "NOLAYER", "NOSCRIPT", "OBJECT", "FRAME",
       "FRAMESET", "PARAM",   "SCRIPT"};
-  const HashSet<String> default_allow_elements_ = {
-      "A",          "ABBR",    "ACRONYM", "ADDRESS",  "AREA",     "ARTICLE",
-      "ASIDE",      "AUDIO",   "B",       "BDI",      "BDO",      "BIG",
-      "BLOCKQUOTE", "BODY",    "BR",      "BUTTON",   "CANVAS",   "CAPTION",
-      "CENTER",     "CITE",    "CODE",    "COL",      "COLGROUP", "DATALIST",
-      "DD",         "DEL",     "DETAILS", "DIALOG",   "DFN",      "DIR",
-      "DIV",        "DL",      "DT",      "EM",       "FIELDSET", "FIGCAPTION",
-      "FIGURE",     "FONT",    "FOOTER",  "FORM",     "H1",       "H2",
-      "H3",         "H4",      "H5",      "H6",       "HEAD",     "HEADER",
-      "HGROUP",     "HR",      "HTML",    "I",        "IMG",      "INPUT",
-      "INS",        "KBD",     "KEYGEN",  "LABEL",    "LEGEND",   "LI",
-      "LINK",       "LISTING", "MAP",     "MARK",     "MENU",     "META",
-      "METER",      "NAV",     "NOBR",    "OL",       "OPTGROUP", "OPTION",
-      "OUTPUT",     "P",       "PICTURE", "PRE",      "PROGRESS", "Q",
-      "RB",         "RP",      "RT",      "RTC",      "RUBY",     "S",
-      "SAMP",       "SECTION", "SELECT",  "SMALL",    "SOURCE",   "SPAN",
-      "STRIKE",     "STRONG",  "SUB",     "SUMMARY",  "SUP",      "STYLE",
-      "TABLE",      "TBODY",   "TD",      "TEXTAREA", "TFOOT",    "TH",
-      "THEAD",      "TIME",    "TR",      "TRACK",    "TT",       "U",
-      "UL",         "VAR",     "VIDEO",   "WBR"};
-  const Vector<String> kVectorStar = Vector<String>({"*"});
   const HashMap<String, Vector<String>> baseline_drop_attributes_ = {
       {"onabort", kVectorStar},
       {"onafterprint", kVectorStar},
@@ -217,310 +196,8 @@
       {"onwebkitfullscreenchange", kVectorStar},
       {"onwebkitfullscreenerror", kVectorStar},
       {"onwebkittransitionend", kVectorStar},
-      {"onwheel", kVectorStar},
-
-      // SVG events: Un-specced. This is temporarily here until the spec has
-      // a full solution to SVG.
-      {"onbegin", kVectorStar},
-      {"onend", kVectorStar},
-      {"onrepeat", kVectorStar},
-      {"onabort", kVectorStar},
-      {"onerror", kVectorStar},
-      {"onresize", kVectorStar},
-      {"onscroll", kVectorStar},
-      {"onunload", kVectorStar},
-      {"oncopy", kVectorStar},
-      {"oncut", kVectorStar},
-      {"onpaste", kVectorStar},
-      {"oncancel", kVectorStar},
-      {"oncanplay", kVectorStar},
-      {"oncanplaythrough", kVectorStar},
-      {"onchange", kVectorStar},
-      {"onclick", kVectorStar},
-      {"onclose", kVectorStar},
-      {"oncuechange", kVectorStar},
-      {"ondblclick", kVectorStar},
-      {"ondrag", kVectorStar},
-      {"ondragend", kVectorStar},
-      {"ondragenter", kVectorStar},
-      {"ondragexit", kVectorStar},
-      {"ondragleave", kVectorStar},
-      {"ondragover", kVectorStar},
-      {"ondragstart", kVectorStar},
-      {"ondrop", kVectorStar},
-      {"ondurationchange", kVectorStar},
-      {"onemptied", kVectorStar},
-      {"onended", kVectorStar},
-      {"onerror", kVectorStar},
-      {"onfocus", kVectorStar},
-      {"oninput", kVectorStar},
-      {"oninvalid", kVectorStar},
-      {"onkeydown", kVectorStar},
-      {"onkeypress", kVectorStar},
-      {"onkeyup", kVectorStar},
-      {"onload", kVectorStar},
-      {"onloadeddata", kVectorStar},
-      {"onloadedmetadata", kVectorStar},
-      {"onloadstart", kVectorStar},
-      {"onmousedown", kVectorStar},
-      {"onmouseenter", kVectorStar},
-      {"onmouseleave", kVectorStar},
-      {"onmousemove", kVectorStar},
-      {"onmouseout", kVectorStar},
-      {"onmouseover", kVectorStar},
-      {"onmouseup", kVectorStar},
-      {"onmousewheel", kVectorStar},
-      {"onpause", kVectorStar},
-      {"onplay", kVectorStar},
-      {"onplaying", kVectorStar},
-      {"onprogress", kVectorStar},
-      {"onratechange", kVectorStar},
-      {"onreset", kVectorStar},
-      {"onresize", kVectorStar},
-      {"onscroll", kVectorStar},
-      {"onseeked", kVectorStar},
-      {"onseeking", kVectorStar},
-      {"onselect", kVectorStar},
-      {"onshow", kVectorStar},
-      {"onstalled", kVectorStar},
-      {"onsubmit", kVectorStar},
-      {"onsuspend", kVectorStar},
-      {"ontimeupdate", kVectorStar},
-      {"ontoggle", kVectorStar},
-      {"onvolumechange", kVectorStar},
-      {"onwaiting", kVectorStar},
-      {"onactivate", kVectorStar},
-      {"onfocusin", kVectorStar},
-      {"onfocusout", kVectorStar},
+      {"onwheel", kVectorStar}};
   };
-  const HashMap<String, Vector<String>> default_allow_attributes_ = {
-      {"abbr", kVectorStar},
-      {"accept", kVectorStar},
-      {"accept-charset", kVectorStar},
-      {"accesskey", kVectorStar},
-      {"action", kVectorStar},
-      {"align", kVectorStar},
-      {"alink", kVectorStar},
-      {"allow", kVectorStar},
-      {"allowfullscreen", kVectorStar},
-      {"alt", kVectorStar},
-      {"anchor", kVectorStar},
-      {"archive", kVectorStar},
-      {"as", kVectorStar},
-      {"async", kVectorStar},
-      {"autocapitalize", kVectorStar},
-      {"autocomplete", kVectorStar},
-      {"autocorrect", kVectorStar},
-      {"autofocus", kVectorStar},
-      {"autopictureinpicture", kVectorStar},
-      {"autoplay", kVectorStar},
-      {"axis", kVectorStar},
-      {"background", kVectorStar},
-      {"behavior", kVectorStar},
-      {"bgcolor", kVectorStar},
-      {"border", kVectorStar},
-      {"bordercolor", kVectorStar},
-      {"capture", kVectorStar},
-      {"cellpadding", kVectorStar},
-      {"cellspacing", kVectorStar},
-      {"challenge", kVectorStar},
-      {"char", kVectorStar},
-      {"charoff", kVectorStar},
-      {"charset", kVectorStar},
-      {"checked", kVectorStar},
-      {"cite", kVectorStar},
-      {"class", kVectorStar},
-      {"classid", kVectorStar},
-      {"clear", kVectorStar},
-      {"code", kVectorStar},
-      {"codebase", kVectorStar},
-      {"codetype", kVectorStar},
-      {"color", kVectorStar},
-      {"cols", kVectorStar},
-      {"colspan", kVectorStar},
-      {"compact", kVectorStar},
-      {"content", kVectorStar},
-      {"contenteditable", kVectorStar},
-      {"controls", kVectorStar},
-      {"controlslist", kVectorStar},
-      {"conversiondestination", kVectorStar},
-      {"coords", kVectorStar},
-      {"crossorigin", kVectorStar},
-      {"csp", kVectorStar},
-      {"data", kVectorStar},
-      {"datetime", kVectorStar},
-      {"declare", kVectorStar},
-      {"decoding", kVectorStar},
-      {"default", kVectorStar},
-      {"defer", kVectorStar},
-      {"dir", kVectorStar},
-      {"direction", kVectorStar},
-      {"dirname", kVectorStar},
-      {"disabled", kVectorStar},
-      {"disablepictureinpicture", kVectorStar},
-      {"disableremoteplayback", kVectorStar},
-      {"disallowdocumentaccess", kVectorStar},
-      {"download", kVectorStar},
-      {"draggable", kVectorStar},
-      {"elementtiming", kVectorStar},
-      {"enctype", kVectorStar},
-      {"end", kVectorStar},
-      {"enterkeyhint", kVectorStar},
-      {"event", kVectorStar},
-      {"exportparts", kVectorStar},
-      {"face", kVectorStar},
-      {"for", kVectorStar},
-      {"form", kVectorStar},
-      {"formaction", kVectorStar},
-      {"formenctype", kVectorStar},
-      {"formmethod", kVectorStar},
-      {"formnovalidate", kVectorStar},
-      {"formtarget", kVectorStar},
-      {"frame", kVectorStar},
-      {"frameborder", kVectorStar},
-      {"headers", kVectorStar},
-      {"height", kVectorStar},
-      {"hidden", kVectorStar},
-      {"high", kVectorStar},
-      {"href", kVectorStar},
-      {"hreflang", kVectorStar},
-      {"hreftranslate", kVectorStar},
-      {"hspace", kVectorStar},
-      {"http-equiv", kVectorStar},
-      {"id", kVectorStar},
-      {"imagesizes", kVectorStar},
-      {"imagesrcset", kVectorStar},
-      {"importance", kVectorStar},
-      {"impressiondata", kVectorStar},
-      {"impressionexpiry", kVectorStar},
-      {"incremental", kVectorStar},
-      {"inert", kVectorStar},
-      {"inputmode", kVectorStar},
-      {"integrity", kVectorStar},
-      {"invisible", kVectorStar},
-      {"is", kVectorStar},
-      {"ismap", kVectorStar},
-      {"keytype", kVectorStar},
-      {"kind", kVectorStar},
-      {"label", kVectorStar},
-      {"lang", kVectorStar},
-      {"language", kVectorStar},
-      {"latencyhint", kVectorStar},
-      {"leftmargin", kVectorStar},
-      {"link", kVectorStar},
-      {"list", kVectorStar},
-      {"loading", kVectorStar},
-      {"longdesc", kVectorStar},
-      {"loop", kVectorStar},
-      {"low", kVectorStar},
-      {"lowsrc", kVectorStar},
-      {"manifest", kVectorStar},
-      {"marginheight", kVectorStar},
-      {"marginwidth", kVectorStar},
-      {"max", kVectorStar},
-      {"maxlength", kVectorStar},
-      {"mayscript", kVectorStar},
-      {"media", kVectorStar},
-      {"method", kVectorStar},
-      {"min", kVectorStar},
-      {"minlength", kVectorStar},
-      {"multiple", kVectorStar},
-      {"muted", kVectorStar},
-      {"name", kVectorStar},
-      {"nohref", kVectorStar},
-      {"nomodule", kVectorStar},
-      {"nonce", kVectorStar},
-      {"noresize", kVectorStar},
-      {"noshade", kVectorStar},
-      {"novalidate", kVectorStar},
-      {"nowrap", kVectorStar},
-      {"object", kVectorStar},
-      {"open", kVectorStar},
-      {"optimum", kVectorStar},
-      {"part", kVectorStar},
-      {"pattern", kVectorStar},
-      {"ping", kVectorStar},
-      {"placeholder", kVectorStar},
-      {"playsinline", kVectorStar},
-      {"policy", kVectorStar},
-      {"poster", kVectorStar},
-      {"preload", kVectorStar},
-      {"pseudo", kVectorStar},
-      {"readonly", kVectorStar},
-      {"referrerpolicy", kVectorStar},
-      {"rel", kVectorStar},
-      {"reportingorigin", kVectorStar},
-      {"required", kVectorStar},
-      {"resources", kVectorStar},
-      {"rev", kVectorStar},
-      {"reversed", kVectorStar},
-      {"role", kVectorStar},
-      {"placeholder", kVectorStar},
-      {"playsinline", kVectorStar},
-      {"policy", kVectorStar},
-      {"poster", kVectorStar},
-      {"preload", kVectorStar},
-      {"pseudo", kVectorStar},
-      {"readonly", kVectorStar},
-      {"referrerpolicy", kVectorStar},
-      {"rel", kVectorStar},
-      {"reportingorigin", kVectorStar},
-      {"required", kVectorStar},
-      {"resources", kVectorStar},
-      {"rev", kVectorStar},
-      {"reversed", kVectorStar},
-      {"role", kVectorStar},
-      {"rows", kVectorStar},
-      {"rowspan", kVectorStar},
-      {"rules", kVectorStar},
-      {"sandbox", kVectorStar},
-      {"scheme", kVectorStar},
-      {"scope", kVectorStar},
-      {"scopes", kVectorStar},
-      {"scrollamount", kVectorStar},
-      {"scrolldelay", kVectorStar},
-      {"scrolling", kVectorStar},
-      {"select", kVectorStar},
-      {"selected", kVectorStar},
-      {"shadowroot", kVectorStar},
-      {"shadowrootdelegatesfocus", kVectorStar},
-      {"shape", kVectorStar},
-      {"size", kVectorStar},
-      {"sizes", kVectorStar},
-      {"slot", kVectorStar},
-      {"span", kVectorStar},
-      {"spellcheck", kVectorStar},
-      {"src", kVectorStar},
-      {"srcdoc", kVectorStar},
-      {"srclang", kVectorStar},
-      {"srcset", kVectorStar},
-      {"standby", kVectorStar},
-      {"start", kVectorStar},
-      {"step", kVectorStar},
-      {"style", kVectorStar},
-      {"summary", kVectorStar},
-      {"tabindex", kVectorStar},
-      {"target", kVectorStar},
-      {"text", kVectorStar},
-      {"title", kVectorStar},
-      {"topmargin", kVectorStar},
-      {"translate", kVectorStar},
-      {"truespeed", kVectorStar},
-      {"trusttoken", kVectorStar},
-      {"type", kVectorStar},
-      {"usemap", kVectorStar},
-      {"valign", kVectorStar},
-      {"value", kVectorStar},
-      {"valuetype", kVectorStar},
-      {"version", kVectorStar},
-      {"virtualkeyboardpolicy", kVectorStar},
-      {"vlink", kVectorStar},
-      {"vspace", kVectorStar},
-      {"webkitdirectory", kVectorStar},
-      {"width", kVectorStar},
-      {"wrap", kVectorStar}};
-};
-
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_SANITIZER_API_SANITIZER_H_
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.idl b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.idl
index bcfd65b..21f02b2 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.idl
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.idl
@@ -12,6 +12,10 @@
   RuntimeEnabled=SanitizerAPI
 ] interface Sanitizer {
   [MeasureAs=SanitizerAPICreated, CallWith=ExecutionContext, RaisesException] constructor(optional SanitizerConfig config = {});
-  [MeasureAs=SanitizerAPIToString, CallWith=ScriptState, RaisesException] DOMString sanitizeToString(SanitizerInput input);
+
   [MeasureAs=SanitizerAPIToFragment, CallWith=ScriptState, RaisesException] DocumentFragment sanitize(SanitizerInputWithTrustedHTML input);
+  [MeasureAs=SanitizerAPIToString, CallWith=ScriptState, RaisesException] DOMString sanitizeToString(SanitizerInput input);
+
+  [MeasureAs=SanitizerAPIGetConfig] SanitizerConfig config();
+  [MeasureAs=SanitizerAPIGetDefaultConfig] static SanitizerConfig defaultConfig();
 };
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
index ea0cba3..c5c3c860 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
@@ -10,5 +10,5 @@
   sequence<DOMString> dropElements;
   record<DOMString, sequence<DOMString>> allowAttributes;
   record<DOMString, sequence<DOMString>> dropAttributes;
-  boolean allowCustomElements = false;
+  boolean allowCustomElements;
 };
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.cc b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.cc
new file mode 100644
index 0000000..9e56ece
--- /dev/null
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.cc
@@ -0,0 +1,370 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h"
+
+#include "third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
+
+namespace blink {
+
+namespace {
+
+const char* kDefaultAllowElements[] = {
+    "a",          "abbr",    "acronym", "address",  "area",     "article",
+    "aside",      "audio",   "b",       "bdi",      "bdo",      "big",
+    "blockquote", "body",    "br",      "button",   "canvas",   "caption",
+    "center",     "cite",    "code",    "col",      "colgroup", "datalist",
+    "dd",         "del",     "details", "dialog",   "dfn",      "dir",
+    "div",        "dl",      "dt",      "em",       "fieldset", "figcaption",
+    "figure",     "font",    "footer",  "form",     "h1",       "h2",
+    "h3",         "h4",      "h5",      "h6",       "head",     "header",
+    "hgroup",     "hr",      "html",    "i",        "img",      "input",
+    "ins",        "kbd",     "keygen",  "label",    "legend",   "li",
+    "link",       "listing", "map",     "mark",     "menu",     "meta",
+    "meter",      "nav",     "nobr",    "ol",       "optgroup", "option",
+    "output",     "p",       "picture", "pre",      "progress", "q",
+    "rb",         "rp",      "rt",      "rtc",      "ruby",     "s",
+    "samp",       "section", "select",  "small",    "source",   "span",
+    "strike",     "strong",  "sub",     "summary",  "sup",      "style",
+    "table",      "tbody",   "td",      "textarea", "tfoot",    "th",
+    "thead",      "time",    "tr",      "track",    "tt",       "u",
+    "ul",         "var",     "video",   "wbr"};
+
+const char* kDefaultAllowAttributes[] = {"abbr",
+                                         "accept",
+                                         "accept-charset",
+                                         "accesskey",
+                                         "action",
+                                         "align",
+                                         "alink",
+                                         "allow",
+                                         "allowfullscreen",
+                                         "alt",
+                                         "anchor",
+                                         "archive",
+                                         "as",
+                                         "async",
+                                         "autocapitalize",
+                                         "autocomplete",
+                                         "autocorrect",
+                                         "autofocus",
+                                         "autopictureinpicture",
+                                         "autoplay",
+                                         "axis",
+                                         "background",
+                                         "behavior",
+                                         "bgcolor",
+                                         "border",
+                                         "bordercolor",
+                                         "capture",
+                                         "cellpadding",
+                                         "cellspacing",
+                                         "challenge",
+                                         "char",
+                                         "charoff",
+                                         "charset",
+                                         "checked",
+                                         "cite",
+                                         "class",
+                                         "classid",
+                                         "clear",
+                                         "code",
+                                         "codebase",
+                                         "codetype",
+                                         "color",
+                                         "cols",
+                                         "colspan",
+                                         "compact",
+                                         "content",
+                                         "contenteditable",
+                                         "controls",
+                                         "controlslist",
+                                         "conversiondestination",
+                                         "coords",
+                                         "crossorigin",
+                                         "csp",
+                                         "data",
+                                         "datetime",
+                                         "declare",
+                                         "decoding",
+                                         "default",
+                                         "defer",
+                                         "dir",
+                                         "direction",
+                                         "dirname",
+                                         "disabled",
+                                         "disablepictureinpicture",
+                                         "disableremoteplayback",
+                                         "disallowdocumentaccess",
+                                         "download",
+                                         "draggable",
+                                         "elementtiming",
+                                         "enctype",
+                                         "end",
+                                         "enterkeyhint",
+                                         "event",
+                                         "exportparts",
+                                         "face",
+                                         "for",
+                                         "form",
+                                         "formaction",
+                                         "formenctype",
+                                         "formmethod",
+                                         "formnovalidate",
+                                         "formtarget",
+                                         "frame",
+                                         "frameborder",
+                                         "headers",
+                                         "height",
+                                         "hidden",
+                                         "high",
+                                         "href",
+                                         "hreflang",
+                                         "hreftranslate",
+                                         "hspace",
+                                         "http-equiv",
+                                         "id",
+                                         "imagesizes",
+                                         "imagesrcset",
+                                         "importance",
+                                         "impressiondata",
+                                         "impressionexpiry",
+                                         "incremental",
+                                         "inert",
+                                         "inputmode",
+                                         "integrity",
+                                         "invisible",
+                                         "is",
+                                         "ismap",
+                                         "keytype",
+                                         "kind",
+                                         "label",
+                                         "lang",
+                                         "language",
+                                         "latencyhint",
+                                         "leftmargin",
+                                         "link",
+                                         "list",
+                                         "loading",
+                                         "longdesc",
+                                         "loop",
+                                         "low",
+                                         "lowsrc",
+                                         "manifest",
+                                         "marginheight",
+                                         "marginwidth",
+                                         "max",
+                                         "maxlength",
+                                         "mayscript",
+                                         "media",
+                                         "method",
+                                         "min",
+                                         "minlength",
+                                         "multiple",
+                                         "muted",
+                                         "name",
+                                         "nohref",
+                                         "nomodule",
+                                         "nonce",
+                                         "noresize",
+                                         "noshade",
+                                         "novalidate",
+                                         "nowrap",
+                                         "object",
+                                         "open",
+                                         "optimum",
+                                         "part",
+                                         "pattern",
+                                         "ping",
+                                         "placeholder",
+                                         "playsinline",
+                                         "policy",
+                                         "poster",
+                                         "preload",
+                                         "pseudo",
+                                         "readonly",
+                                         "referrerpolicy",
+                                         "rel",
+                                         "reportingorigin",
+                                         "required",
+                                         "resources",
+                                         "rev",
+                                         "reversed",
+                                         "role",
+                                         "placeholder",
+                                         "playsinline",
+                                         "policy",
+                                         "poster",
+                                         "preload",
+                                         "pseudo",
+                                         "readonly",
+                                         "referrerpolicy",
+                                         "rel",
+                                         "reportingorigin",
+                                         "required",
+                                         "resources",
+                                         "rev",
+                                         "reversed",
+                                         "role",
+                                         "rows",
+                                         "rowspan",
+                                         "rules",
+                                         "sandbox",
+                                         "scheme",
+                                         "scope",
+                                         "scopes",
+                                         "scrollamount",
+                                         "scrolldelay",
+                                         "scrolling",
+                                         "select",
+                                         "selected",
+                                         "shadowroot",
+                                         "shadowrootdelegatesfocus",
+                                         "shape",
+                                         "size",
+                                         "sizes",
+                                         "slot",
+                                         "span",
+                                         "spellcheck",
+                                         "src",
+                                         "srcdoc",
+                                         "srclang",
+                                         "srcset",
+                                         "standby",
+                                         "start",
+                                         "step",
+                                         "style",
+                                         "summary",
+                                         "tabindex",
+                                         "target",
+                                         "text",
+                                         "title",
+                                         "topmargin",
+                                         "translate",
+                                         "truespeed",
+                                         "trusttoken",
+                                         "type",
+                                         "usemap",
+                                         "valign",
+                                         "value",
+                                         "valuetype",
+                                         "version",
+                                         "virtualkeyboardpolicy",
+                                         "vlink",
+                                         "vspace",
+                                         "webkitdirectory",
+                                         "width",
+                                         "wrap"};
+
+void ElementFormatter(HashSet<String>& element_set,
+                      const Vector<String>& elements) {
+  for (const String& s : elements) {
+    element_set.insert(s.UpperASCII());
+  }
+}
+
+void AttrFormatter(HashMap<String, Vector<String>>& attr_map,
+                   const Vector<std::pair<String, Vector<String>>>& attrs) {
+  Vector<String> kVectorStar = {"*"};
+  for (const std::pair<String, Vector<String>>& pair : attrs) {
+    const String& lower_attr = pair.first.LowerASCII();
+    if (pair.second == kVectorStar || pair.second.Contains("*")) {
+      attr_map.insert(lower_attr, kVectorStar);
+    } else {
+      Vector<String> elements;
+      for (const String& s : pair.second) {
+        elements.push_back(s.UpperASCII());
+      }
+      attr_map.insert(lower_attr, elements);
+    }
+  }
+}
+
+SanitizerConfig* BuildDefaultConfig() {
+  SanitizerConfig* config = SanitizerConfig::Create();
+
+  Vector<String> allow_elements;
+  for (const auto* elem : kDefaultAllowElements)
+    allow_elements.push_back(elem);
+  config->setAllowElements(allow_elements);
+
+  Vector<String> star = {"*"};
+  Vector<std::pair<String, Vector<String>>> allow_attributes;
+  for (const auto* attr : kDefaultAllowAttributes)
+    allow_attributes.push_back(std::make_pair(attr, star));
+  config->setAllowAttributes(allow_attributes);
+
+  config->setAllowCustomElements(false);
+  return config;
+}
+
+SanitizerConfig* GetDefaultConfig() {
+  DEFINE_STATIC_LOCAL(Persistent<SanitizerConfig>, config_,
+                      (BuildDefaultConfig()));
+  return config_.Get();
+}
+
+SanitizerConfigImpl GetDefaultConfigImpl() {
+  DEFINE_STATIC_LOCAL(SanitizerConfigImpl, config_,
+                      (SanitizerConfigImpl::From(GetDefaultConfig())));
+  return config_;
+}
+
+}  // anonymous namespace
+
+// Create a SanitizerConfigImpl from a SanitizerConfig.
+//
+// The SC is a JavaScript dictionary - as defined in IDL and required by the
+// spec - which contains all the information, but is not efficiently queryable.
+// The SCImpl uses more suitable data structures, but this requires us to
+// duplicate the information. This method accompslished this.
+SanitizerConfigImpl SanitizerConfigImpl::From(const SanitizerConfig* config) {
+  if (!config) {
+    return GetDefaultConfigImpl();
+  }
+
+  SanitizerConfigImpl impl;
+
+  impl.allow_custom_elements_ =
+      config->hasAllowCustomElements() && config->allowCustomElements();
+
+  // Format dropElements to uppercase.
+  if (config->hasDropElements()) {
+    ElementFormatter(impl.drop_elements_, config->dropElements());
+  }
+
+  // Format blockElements to uppercase.
+  if (config->hasBlockElements()) {
+    ElementFormatter(impl.block_elements_, config->blockElements());
+  }
+
+  // Format allowElements to uppercase.
+  if (config->hasAllowElements()) {
+    ElementFormatter(impl.allow_elements_, config->allowElements());
+  } else {
+    impl.allow_elements_ = GetDefaultConfigImpl().allow_elements_;
+  }
+
+  // Format dropAttributes to lowercase.
+  if (config->hasDropAttributes()) {
+    AttrFormatter(impl.drop_attributes_, config->dropAttributes());
+  }
+
+  // Format allowAttributes to lowercase.
+  if (config->hasAllowAttributes()) {
+    AttrFormatter(impl.allow_attributes_, config->allowAttributes());
+  } else {
+    impl.allow_attributes_ = GetDefaultConfigImpl().allow_attributes_;
+  }
+
+  return impl;
+}
+
+SanitizerConfig* SanitizerConfigImpl::defaultConfig() {
+  return GetDefaultConfig();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h
new file mode 100644
index 0000000..b2d0bff6
--- /dev/null
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SANITIZER_API_SANITIZER_CONFIG_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_SANITIZER_API_SANITIZER_CONFIG_IMPL_H_
+
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class SanitizerConfig;
+
+/**
+ * Helper structure for our Sanitizer implementation.
+ *
+ * The SanitizerConfig (defined in santizer_config.idl) defined the
+ * configuration of a Sanitizer instance, as required by the spec and
+ * JavaScript. This defines an equivalent class, which is meant to contain the
+ * same information but retain it in a fashion more suitable for processing,
+ * e.g. in HashSet<String> rather then Vector<String>.
+ */
+struct SanitizerConfigImpl {
+  // These members store the information from the original SanitizerConfig.
+  HashSet<String> allow_elements_;
+  HashSet<String> block_elements_;
+  HashSet<String> drop_elements_;
+  HashMap<String, Vector<String>> allow_attributes_;
+  HashMap<String, Vector<String>> drop_attributes_;
+  bool allow_custom_elements_;
+
+  // Create a SantizerConfigImpl from a SanitizerConfig.
+  // Will use the default config if it received nullptr.
+  static SanitizerConfigImpl From(const SanitizerConfig*);
+
+  static SanitizerConfig* defaultConfig();
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_SANITIZER_API_SANITIZER_CONFIG_IMPL_H_
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 42b197fd..9da03c4 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -852,8 +852,6 @@
     "graphics/compositor_filter_operations.h",
     "graphics/compositor_mutator_client.cc",
     "graphics/compositor_mutator_client.h",
-    "graphics/contiguous_container.cc",
-    "graphics/contiguous_container.h",
     "graphics/cpu/arm/webgl_image_conversion_neon.h",
     "graphics/cpu/mips/webgl_image_conversion_msa.h",
     "graphics/cpu/x86/webgl_image_conversion_sse.h",
@@ -2037,7 +2035,6 @@
     "graphics/compositing/paint_artifact_compositor_test.cc",
     "graphics/compositing/paint_chunks_to_cc_layer_test.cc",
     "graphics/compositor_element_id_test.cc",
-    "graphics/contiguous_container_test.cc",
     "graphics/dark_mode_color_classifier_test.cc",
     "graphics/dark_mode_filter_test.cc",
     "graphics/dark_mode_image_cache_test.cc",
diff --git a/third_party/blink/renderer/platform/graphics/contiguous_container.cc b/third_party/blink/renderer/platform/graphics/contiguous_container.cc
deleted file mode 100644
index f0ee0bdb..0000000
--- a/third_party/blink/renderer/platform/graphics/contiguous_container.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/graphics/contiguous_container.h"
-
-#include <algorithm>
-#include <memory>
-
-namespace blink {
-
-ContiguousContainerBase::ContiguousContainerBase(
-    wtf_size_t max_item_size,
-    wtf_size_t initial_capacity_in_bytes)
-    : max_item_size_(max_item_size),
-      initial_capacity_in_bytes_(
-          std::max(max_item_size, initial_capacity_in_bytes)) {}
-
-ContiguousContainerBase::~ContiguousContainerBase() = default;
-
-wtf_size_t ContiguousContainerBase::CapacityInBytes() const {
-  wtf_size_t capacity = 0;
-  for (const auto& buffer : buffers_)
-    capacity += buffer.Capacity();
-  return capacity;
-}
-
-wtf_size_t ContiguousContainerBase::UsedCapacityInBytes() const {
-  wtf_size_t used_capacity = 0;
-  for (const auto& buffer : buffers_)
-    used_capacity += buffer.UsedCapacity();
-  return used_capacity;
-}
-
-wtf_size_t ContiguousContainerBase::MemoryUsageInBytes() const {
-  return sizeof(*this) + CapacityInBytes() + items_.CapacityInBytes();
-}
-
-uint8_t* ContiguousContainerBase::Allocate(wtf_size_t item_size,
-                                           const char* type_name) {
-  DCHECK_LE(item_size, max_item_size_);
-
-  Buffer* buffer_for_alloc = nullptr;
-  if (!buffers_.IsEmpty() && buffers_.back().UnusedCapacity() >= item_size)
-    buffer_for_alloc = &buffers_.back();
-
-  if (!buffer_for_alloc) {
-    wtf_size_t new_buffer_size = buffers_.IsEmpty()
-                                     ? initial_capacity_in_bytes_
-                                     : 2 * buffers_.back().Capacity();
-    buffer_for_alloc = &buffers_.emplace_back(new_buffer_size, type_name);
-  }
-
-  uint8_t* item = buffer_for_alloc->Allocate(item_size);
-  items_.push_back(item);
-  return item;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/contiguous_container.h b/third_party/blink/renderer/platform/graphics/contiguous_container.h
deleted file mode 100644
index 12c03b02..0000000
--- a/third_party/blink/renderer/platform/graphics/contiguous_container.h
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CONTIGUOUS_CONTAINER_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CONTIGUOUS_CONTAINER_H_
-
-#include <cstddef>
-#include <iterator>
-#include <memory>
-#include <utility>
-
-#include "base/compiler_specific.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/allocator/partitions.h"
-#include "third_party/blink/renderer/platform/wtf/container_annotations.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-
-namespace blink {
-
-// ContiguousContainer is a container which stores a list of heterogeneous
-// items (in particular, of varying sizes), packed next to one another in
-// memory. Items are never relocated, so it is safe to store pointers to them
-// for the lifetime of the container (unless the item is removed).
-//
-// Memory is allocated in a series of buffers (with exponential growth). When an
-// item is allocated, it is given only the space it requires (possibly with
-// enough padding to preserve alignment), rather than the maximum possible size.
-// This allows small and large items to coexist without wasting much space.
-//
-// Since it stores pointers to all of the items it allocates in a vector, it
-// supports efficient iteration and indexing. However, for mutation the
-// supported operations are limited to appending to the end of the list and
-// replacing the last item.
-//
-// Clients should instantiate ContiguousContainer; ContiguousContainerBase is an
-// artifact of the implementation.
-
-class PLATFORM_EXPORT ContiguousContainerBase {
-  DISALLOW_NEW();
-
- public:
-  ContiguousContainerBase(const ContiguousContainerBase&) = delete;
-  ContiguousContainerBase& operator=(const ContiguousContainerBase&) = delete;
-  ContiguousContainerBase(ContiguousContainerBase&&) = delete;
-  ContiguousContainerBase& operator=(ContiguousContainerBase&&) = delete;
-
- protected:
-  // The initial capacity will be allocated when the first item is added.
-  ContiguousContainerBase(wtf_size_t max_item_size,
-                          wtf_size_t initial_capacity_in_bytes);
-  ~ContiguousContainerBase();
-
-  wtf_size_t size() const { return items_.size(); }
-  bool IsEmpty() const { return !size(); }
-  wtf_size_t CapacityInBytes() const;
-  wtf_size_t UsedCapacityInBytes() const;
-  wtf_size_t MemoryUsageInBytes() const;
-
-  // These do not invoke constructors or destructors.
-  uint8_t* Allocate(wtf_size_t item_size, const char* type_name);
-
-  wtf_size_t LastItemSize() const {
-    return static_cast<wtf_size_t>(buffers_.back().End() - items_.back());
-  }
-
-  using ItemVector = Vector<uint8_t*>;
-  ItemVector items_;
-
- private:
-  class Buffer {
-   public:
-    Buffer(wtf_size_t buffer_size, const char* type_name)
-        : capacity_(static_cast<wtf_size_t>(
-              WTF::Partitions::BufferPotentialCapacity(buffer_size))),
-          begin_(static_cast<uint8_t*>(
-              WTF::Partitions::BufferMalloc(capacity_, type_name))),
-          end_(begin_) {
-      ANNOTATE_NEW_BUFFER(begin_, capacity_, 0);
-    }
-
-    ~Buffer() {
-      ANNOTATE_DELETE_BUFFER(begin_, capacity_, UsedCapacity());
-      WTF::Partitions::BufferFree(begin_);
-    }
-
-    wtf_size_t Capacity() const { return capacity_; }
-    wtf_size_t UsedCapacity() const {
-      return static_cast<wtf_size_t>(end_ - begin_);
-    }
-    wtf_size_t UnusedCapacity() const { return Capacity() - UsedCapacity(); }
-    bool IsEmpty() const { return UsedCapacity() == 0; }
-
-    uint8_t* Allocate(wtf_size_t item_size) {
-      DCHECK_GE(UnusedCapacity(), item_size);
-      ANNOTATE_CHANGE_SIZE(begin_, capacity_, UsedCapacity(),
-                           UsedCapacity() + item_size);
-      uint8_t* result = end_;
-      end_ += item_size;
-      return result;
-    }
-
-    uint8_t* End() const { return end_; }
-
-   private:
-    // begin_ <= end_ <= begin_ + capacity_
-    wtf_size_t capacity_;
-    uint8_t* begin_;
-    uint8_t* end_;
-  };
-
-  Vector<Buffer> buffers_;
-  wtf_size_t max_item_size_;
-  wtf_size_t initial_capacity_in_bytes_;
-};
-
-// For most cases, no alignment stricter than pointer alignment is required. If
-// one of the derived classes has stronger alignment requirements (and the
-// static_assert fires), set alignment to the LCM of the derived class
-// alignments. For small structs without pointers, it may be possible to reduce
-// alignment for tighter packing.
-
-template <class BaseItemType, unsigned alignment = sizeof(void*)>
-class ContiguousContainer : public ContiguousContainerBase {
- private:
-  // Declares itself as a forward iterator, but also supports a few more
-  // things. The whole random access iterator interface is a bit much.
-  template <typename BaseIterator, typename ValueType>
-  class IteratorWrapper
-      : public std::iterator<std::forward_iterator_tag, ValueType> {
-    DISALLOW_NEW();
-
-   public:
-    IteratorWrapper() = default;
-    bool operator==(const IteratorWrapper& other) const {
-      return it_ == other.it_;
-    }
-    bool operator!=(const IteratorWrapper& other) const {
-      return it_ != other.it_;
-    }
-    bool operator<(const IteratorWrapper& other) const {
-      return it_ < other.it_;
-    }
-    ValueType& operator*() const { return *reinterpret_cast<ValueType*>(*it_); }
-    ValueType* operator->() const { return &operator*(); }
-    IteratorWrapper operator+(std::ptrdiff_t n) const {
-      return IteratorWrapper(it_ + n);
-    }
-    IteratorWrapper operator++(int) {
-      IteratorWrapper tmp = *this;
-      ++it_;
-      return tmp;
-    }
-    std::ptrdiff_t operator-(const IteratorWrapper& other) const {
-      return it_ - other.it_;
-    }
-    IteratorWrapper& operator++() {
-      ++it_;
-      return *this;
-    }
-
-   private:
-    explicit IteratorWrapper(const BaseIterator& it) : it_(it) {}
-    BaseIterator it_;
-    friend class ContiguousContainer;
-  };
-
- public:
-  using iterator = IteratorWrapper<ItemVector::iterator, BaseItemType>;
-  using const_iterator =
-      IteratorWrapper<ItemVector::const_iterator, const BaseItemType>;
-  using reverse_iterator =
-      IteratorWrapper<ItemVector::reverse_iterator, BaseItemType>;
-  using const_reverse_iterator =
-      IteratorWrapper<ItemVector::const_reverse_iterator, const BaseItemType>;
-
-  using value_type = BaseItemType;
-
-  ContiguousContainer(wtf_size_t max_item_size,
-                      wtf_size_t initial_capacity_in_bytes)
-      : ContiguousContainerBase(Align(max_item_size),
-                                initial_capacity_in_bytes) {}
-  ~ContiguousContainer() {
-    for (auto& item : *this) {
-      (void)item;  // MSVC incorrectly reports this variable as unused.
-      item.~BaseItemType();
-    }
-  }
-
-  using ContiguousContainerBase::CapacityInBytes;
-  using ContiguousContainerBase::IsEmpty;
-  using ContiguousContainerBase::MemoryUsageInBytes;
-  using ContiguousContainerBase::size;
-  using ContiguousContainerBase::UsedCapacityInBytes;
-
-  iterator begin() { return iterator(items_.begin()); }
-  iterator end() { return iterator(items_.end()); }
-  const_iterator begin() const { return const_iterator(items_.begin()); }
-  const_iterator end() const { return const_iterator(items_.end()); }
-  reverse_iterator rbegin() { return reverse_iterator(items_.rbegin()); }
-  reverse_iterator rend() { return reverse_iterator(items_.rend()); }
-  const_reverse_iterator rbegin() const {
-    return const_reverse_iterator(items_.rbegin());
-  }
-  const_reverse_iterator rend() const {
-    return const_reverse_iterator(items_.rend());
-  }
-
-  BaseItemType& front() { return *begin(); }
-  const BaseItemType& front() const { return *begin(); }
-  BaseItemType& back() { return *rbegin(); }
-  const BaseItemType& back() const { return *rbegin(); }
-  BaseItemType& operator[](wtf_size_t index) { return *(begin() + index); }
-  const BaseItemType& operator[](wtf_size_t index) const {
-    return *(begin() + index);
-  }
-
-  template <class DerivedItemType, typename... Args>
-  DerivedItemType& AllocateAndConstruct(Args&&... args) {
-    static_assert(WTF::IsSubclass<DerivedItemType, BaseItemType>::value,
-                  "Must use subclass of BaseItemType.");
-    static_assert(alignment % alignof(DerivedItemType) == 0,
-                  "Derived type requires stronger alignment.");
-    return *new (AlignedAllocate(sizeof(DerivedItemType)))
-        DerivedItemType(std::forward<Args>(args)...);
-  }
-
-  // Appends a new item using memcpy, then default-constructs a base item
-  // in its place. Use with care.
-  BaseItemType& AppendByMoving(BaseItemType& item, wtf_size_t size) {
-    DCHECK_GE(size, sizeof(BaseItemType));
-    void* new_item = AlignedAllocate(size);
-    memcpy(new_item, static_cast<void*>(&item), size);
-    new (&item) BaseItemType;
-    return *static_cast<BaseItemType*>(new_item);
-  }
-
-  // The caller must ensure that |size| (the actual size of |item|) is the same
-  // as or smaller than the replaced item.
-  BaseItemType& ReplaceLastByMoving(BaseItemType& item, wtf_size_t size) {
-    DCHECK_GE(size, sizeof(BaseItemType));
-    DCHECK_GE(LastItemSize(), size);
-    back().~BaseItemType();
-    memcpy(static_cast<void*>(&back()), static_cast<void*>(&item), size);
-    new (&item) BaseItemType;
-    return back();
-  }
-
- private:
-  void* AlignedAllocate(wtf_size_t size) {
-    void* result = ContiguousContainerBase::Allocate(
-        Align(size), WTF_HEAP_PROFILER_TYPE_NAME(BaseItemType));
-    DCHECK_EQ(reinterpret_cast<intptr_t>(result) & (alignment - 1), 0u);
-    return result;
-  }
-
-  static wtf_size_t Align(wtf_size_t size) {
-    wtf_size_t aligned_size = alignment * ((size + alignment - 1) / alignment);
-    DCHECK_EQ(aligned_size % alignment, 0u);
-    DCHECK_GE(aligned_size, size);
-    DCHECK_LT(aligned_size, size + alignment);
-    return aligned_size;
-  }
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CONTIGUOUS_CONTAINER_H_
diff --git a/third_party/blink/renderer/platform/graphics/contiguous_container_test.cc b/third_party/blink/renderer/platform/graphics/contiguous_container_test.cc
deleted file mode 100644
index 5fd7cbbe..0000000
--- a/third_party/blink/renderer/platform/graphics/contiguous_container_test.cc
+++ /dev/null
@@ -1,360 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/graphics/contiguous_container.h"
-
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-#include "third_party/blink/renderer/platform/wtf/type_traits.h"
-
-namespace blink {
-namespace {
-
-struct Point2D {
-  Point2D() : Point2D(0, 0) {}
-  Point2D(int x, int y) : x(x), y(y) {}
-  int x, y;
-};
-
-struct Point3D : public Point2D {
-  Point3D() : Point3D(0, 0, 0) {}
-  Point3D(int x, int y, int z) : Point2D(x, y), z(z) {}
-  int z;
-};
-
-// Maximum size of a subclass of Point2D.
-static const wtf_size_t kMaxPointSize = sizeof(Point3D);
-
-// Alignment for Point2D and its subclasses.
-static const wtf_size_t kPointAlignment = sizeof(int);
-
-// How many elements to use for tests with "plenty" of elements.
-static const wtf_size_t kNumElements = 150;
-
-static const wtf_size_t kDefaultInitialCapacityInBytes = 256;
-
-class PointList : public ContiguousContainer<Point2D, kPointAlignment> {
- public:
-  explicit PointList(
-      wtf_size_t initial_capacity_in_bytes = kDefaultInitialCapacityInBytes)
-      : ContiguousContainer(kMaxPointSize, initial_capacity_in_bytes) {}
-};
-
-TEST(ContiguousContainerTest, SimpleStructs) {
-  PointList list;
-  list.AllocateAndConstruct<Point2D>(1, 2);
-  list.AllocateAndConstruct<Point3D>(3, 4, 5);
-  list.AllocateAndConstruct<Point2D>(6, 7);
-
-  ASSERT_EQ(3u, list.size());
-  EXPECT_EQ(1, list[0].x);
-  EXPECT_EQ(2, list[0].y);
-  EXPECT_EQ(3, list[1].x);
-  EXPECT_EQ(4, list[1].y);
-  EXPECT_EQ(5, static_cast<Point3D&>(list[1]).z);
-  EXPECT_EQ(6, list[2].x);
-  EXPECT_EQ(7, list[2].y);
-}
-
-TEST(ContiguousContainerTest, AllocateLots) {
-  PointList list;
-  for (int i = 0; i < static_cast<int>(kNumElements); i++)
-    list.AllocateAndConstruct<Point2D>(i, i);
-  ASSERT_EQ(kNumElements, list.size());
-  for (int i = 0; i < static_cast<int>(kNumElements); i++) {
-    ASSERT_EQ(i, list[i].x);
-    ASSERT_EQ(i, list[i].y);
-  }
-}
-
-class MockDestructible {
-  USING_FAST_MALLOC(MockDestructible);
-
- public:
-  ~MockDestructible() { Destruct(); }
-  MOCK_METHOD0(Destruct, void());
-};
-
-class MockDestructibleList : public ContiguousContainer<MockDestructible> {
- public:
-  explicit MockDestructibleList(
-      wtf_size_t initial_capacity_in_bytes = kDefaultInitialCapacityInBytes)
-      : ContiguousContainer(sizeof(MockDestructible),
-                            initial_capacity_in_bytes) {}
-};
-
-TEST(ContiguousContainerTest, DestructorCalled) {
-  MockDestructibleList list;
-  auto& destructible = list.AllocateAndConstruct<MockDestructible>();
-  EXPECT_EQ(&destructible, &list.front());
-  EXPECT_CALL(destructible, Destruct());
-}
-
-TEST(ContiguousContainerTest, InsertionAndIndexedAccess) {
-  PointList list;
-
-  auto& point1 = list.AllocateAndConstruct<Point2D>();
-  auto& point2 = list.AllocateAndConstruct<Point2D>();
-  auto& point3 = list.AllocateAndConstruct<Point2D>();
-
-  EXPECT_EQ(3u, list.size());
-  EXPECT_EQ(&point1, &list.front());
-  EXPECT_EQ(&point3, &list.back());
-  EXPECT_EQ(&point1, &list[0]);
-  EXPECT_EQ(&point2, &list[1]);
-  EXPECT_EQ(&point3, &list[2]);
-}
-
-TEST(ContiguousContainerTest, Insertion) {
-  PointList list;
-  EXPECT_TRUE(list.IsEmpty());
-  EXPECT_EQ(0u, list.size());
-  EXPECT_EQ(0u, list.CapacityInBytes());
-  EXPECT_EQ(0u, list.UsedCapacityInBytes());
-
-  list.AllocateAndConstruct<Point2D>();
-  EXPECT_FALSE(list.IsEmpty());
-  EXPECT_EQ(1u, list.size());
-  EXPECT_GE(list.CapacityInBytes(), kDefaultInitialCapacityInBytes);
-  EXPECT_EQ(sizeof(Point2D), list.UsedCapacityInBytes());
-}
-
-TEST(ContiguousContainerTest, ElementAddressesAreStable) {
-  PointList list;
-  Vector<Point2D*> pointers;
-  for (int i = 0; i < static_cast<int>(kNumElements); i++)
-    pointers.push_back(&list.AllocateAndConstruct<Point2D>());
-  EXPECT_EQ(kNumElements, list.size());
-  EXPECT_EQ(kNumElements, pointers.size());
-
-  auto list_it = list.begin();
-  auto** vector_it = pointers.begin();
-  for (; list_it != list.end(); ++list_it, ++vector_it)
-    EXPECT_EQ(&*list_it, *vector_it);
-}
-
-TEST(ContiguousContainerTest, ForwardIteration) {
-  PointList list;
-  for (int i = 0; i < static_cast<int>(kNumElements); i++)
-    list.AllocateAndConstruct<Point2D>(i, i);
-  wtf_size_t count = 0;
-  for (Point2D& point : list) {
-    EXPECT_EQ(static_cast<int>(count), point.x);
-    count++;
-  }
-  EXPECT_EQ(kNumElements, count);
-
-  static_assert(std::is_same<decltype(*list.begin()), Point2D&>::value,
-                "Non-const iteration should produce non-const references.");
-}
-
-TEST(ContiguousContainerTest, ConstForwardIteration) {
-  PointList list;
-  for (int i = 0; i < static_cast<int>(kNumElements); i++)
-    list.AllocateAndConstruct<Point2D>(i, i);
-
-  const auto& const_list = list;
-  wtf_size_t count = 0;
-  for (const Point2D& point : const_list) {
-    EXPECT_EQ(static_cast<int>(count), point.x);
-    count++;
-  }
-  EXPECT_EQ(kNumElements, count);
-
-  static_assert(
-      std::is_same<decltype(*const_list.begin()), const Point2D&>::value,
-      "Const iteration should produce const references.");
-}
-
-TEST(ContiguousContainerTest, ReverseIteration) {
-  PointList list;
-  for (int i = 0; i < static_cast<int>(kNumElements); i++)
-    list.AllocateAndConstruct<Point2D>(i, i);
-
-  wtf_size_t count = 0;
-  for (auto it = list.rbegin(); it != list.rend(); ++it) {
-    EXPECT_EQ(static_cast<int>(kNumElements - 1 - count), it->x);
-    count++;
-  }
-  EXPECT_EQ(kNumElements, count);
-
-  static_assert(std::is_same<decltype(*list.rbegin()), Point2D&>::value,
-                "Non-const iteration should produce non-const references.");
-}
-
-// Checks that the latter list has pointers to the elements of the former.
-template <typename It1, typename It2>
-bool EqualPointers(It1 it1, const It1& end1, It2 it2) {
-  for (; it1 != end1; ++it1, ++it2) {
-    if (&*it1 != *it2)
-      return false;
-  }
-  return true;
-}
-
-TEST(ContiguousContainerTest, AppendByMovingSameList) {
-  PointList list;
-  list.AllocateAndConstruct<Point3D>(1, 2, 3);
-
-  // Moves the Point3D to the end, and default-constructs a Point2D in its
-  // place.
-  list.AppendByMoving(list.front(), sizeof(Point3D));
-  EXPECT_EQ(1, list.back().x);
-  EXPECT_EQ(2, list.back().y);
-  EXPECT_EQ(3, static_cast<const Point3D&>(list.back()).z);
-  EXPECT_EQ(2u, list.size());
-
-  // Moves that Point2D to the end, and default-constructs another in its
-  // place.
-  list.front().x = 4;
-  list.AppendByMoving(list.front(), sizeof(Point2D));
-  EXPECT_EQ(4, list.back().x);
-  EXPECT_EQ(3u, list.size());
-}
-
-TEST(ContiguousContainerTest, AppendByMovingDoesNotDestruct) {
-  // GMock mock objects (e.g. MockDestructible) aren't guaranteed to be safe
-  // to memcpy (which is required for appendByMoving).
-  class DestructionNotifier {
-    USING_FAST_MALLOC(DestructionNotifier);
-
-   public:
-    DestructionNotifier(bool* flag = nullptr) : flag_(flag) {}
-    ~DestructionNotifier() {
-      if (flag_)
-        *flag_ = true;
-    }
-
-   private:
-    bool* flag_;
-  };
-
-  bool destroyed = false;
-  ContiguousContainer<DestructionNotifier> list1(
-      sizeof(DestructionNotifier), kDefaultInitialCapacityInBytes);
-  list1.AllocateAndConstruct<DestructionNotifier>(&destroyed);
-  {
-    // Make sure destructor isn't called during appendByMoving.
-    ContiguousContainer<DestructionNotifier> list2(
-        sizeof(DestructionNotifier), kDefaultInitialCapacityInBytes);
-    list2.AppendByMoving(list1.back(), sizeof(DestructionNotifier));
-    EXPECT_FALSE(destroyed);
-  }
-  // But it should be destroyed when list2 is.
-  EXPECT_TRUE(destroyed);
-}
-
-TEST(ContiguousContainerTest, AppendByMovingReturnsMovedPointer) {
-  PointList list1;
-  PointList list2;
-
-  Point2D& point = list1.AllocateAndConstruct<Point2D>();
-  Point2D& moved_point1 = list2.AppendByMoving(point, sizeof(Point2D));
-  EXPECT_EQ(&moved_point1, &list2.back());
-
-  Point2D& moved_point2 = list1.AppendByMoving(moved_point1, sizeof(Point2D));
-  EXPECT_EQ(&moved_point2, &list1.back());
-  EXPECT_NE(&moved_point1, &moved_point2);
-}
-
-TEST(ContiguousContainerTest, AppendByMovingReplacesSourceWithNewElement) {
-  PointList list1;
-  PointList list2;
-
-  list1.AllocateAndConstruct<Point2D>(1, 2);
-  EXPECT_EQ(1, list1.front().x);
-  EXPECT_EQ(2, list1.front().y);
-
-  list2.AppendByMoving(list1.front(), sizeof(Point2D));
-  EXPECT_EQ(0, list1.front().x);
-  EXPECT_EQ(0, list1.front().y);
-  EXPECT_EQ(1, list2.front().x);
-  EXPECT_EQ(2, list2.front().y);
-
-  EXPECT_EQ(1u, list1.size());
-  EXPECT_EQ(1u, list2.size());
-}
-
-TEST(ContiguousContainerTest, AppendByMovingElementsOfDifferentSizes) {
-  PointList list;
-  list.AllocateAndConstruct<Point3D>(1, 2, 3);
-  list.AllocateAndConstruct<Point2D>(4, 5);
-
-  EXPECT_EQ(1, list[0].x);
-  EXPECT_EQ(2, list[0].y);
-  EXPECT_EQ(3, static_cast<const Point3D&>(list[0]).z);
-  EXPECT_EQ(4, list[1].x);
-  EXPECT_EQ(5, list[1].y);
-
-  // Test that moving the first element actually moves the entire object, not
-  // just the base element.
-  list.AppendByMoving(list[0], sizeof(Point3D));
-  EXPECT_EQ(1, list[2].x);
-  EXPECT_EQ(2, list[2].y);
-  EXPECT_EQ(3, static_cast<const Point3D&>(list[2]).z);
-  EXPECT_EQ(4, list[1].x);
-  EXPECT_EQ(5, list[1].y);
-
-  list.AppendByMoving(list[1], sizeof(Point2D));
-  EXPECT_EQ(1, list[2].x);
-  EXPECT_EQ(2, list[2].y);
-  EXPECT_EQ(3, static_cast<const Point3D&>(list[2]).z);
-  EXPECT_EQ(4, list[3].x);
-  EXPECT_EQ(5, list[3].y);
-}
-
-TEST(ContiguousContainerTest, CapacityInBytes) {
-  const int kIterations = 500;
-  const wtf_size_t kInitialCapacity = 10 * kMaxPointSize;
-  const wtf_size_t kUpperBoundOnMinCapacity = kInitialCapacity;
-  // In worst case, there are 2 buffers, and the second buffer contains only one
-  // element, so the factor is close to 3 as the second buffer is twice as big
-  // as the first buffer.
-  const size_t kMaxWasteFactor = 3;
-
-  PointList list(kInitialCapacity);
-
-  // The capacity should grow with the list.
-  for (int i = 0; i < kIterations; i++) {
-    size_t capacity = list.CapacityInBytes();
-    ASSERT_GE(capacity, list.size() * sizeof(Point2D));
-    ASSERT_LE(capacity, std::max<wtf_size_t>(list.size() * sizeof(Point2D),
-                                             kUpperBoundOnMinCapacity) *
-                            kMaxWasteFactor);
-    list.AllocateAndConstruct<Point2D>();
-  }
-}
-
-TEST(ContiguousContainerTest, Alignment) {
-  const size_t kMaxAlign = alignof(long double);
-  ContiguousContainer<Point2D, kMaxAlign> list(kMaxPointSize,
-                                               kDefaultInitialCapacityInBytes);
-
-  list.AllocateAndConstruct<Point2D>();
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AllocateAndConstruct<Point2D>();
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AllocateAndConstruct<Point3D>();
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AllocateAndConstruct<Point3D>();
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AllocateAndConstruct<Point2D>();
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-
-  list.AppendByMoving(list[0], sizeof(Point2D));
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AppendByMoving(list[1], sizeof(Point2D));
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AppendByMoving(list[2], sizeof(Point3D));
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AppendByMoving(list[3], sizeof(Point3D));
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-  list.AppendByMoving(list[4], sizeof(Point2D));
-  EXPECT_EQ(0u, reinterpret_cast<intptr_t>(&list.back()) & (kMaxAlign - 1));
-}
-
-}  // namespace
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item.h b/third_party/blink/renderer/platform/graphics/paint/display_item.h
index a8eb1c7..c99cf8b 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item.h
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item.h
@@ -7,7 +7,6 @@
 
 #include "base/dcheck_is_on.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
-#include "third_party/blink/renderer/platform/graphics/contiguous_container.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -157,14 +156,12 @@
   // later paint cycles when |client| may have been destroyed.
   DisplayItem(const DisplayItemClient& client,
               Type type,
-              wtf_size_t derived_size,
               const IntRect& visual_rect,
               bool draws_content = false)
       : client_(&client),
         visual_rect_(visual_rect),
         fragment_(0),
         type_(type),
-        derived_size_(derived_size),
         raster_effect_outset_(
             static_cast<unsigned>(client.VisualRectOutsetForRasterEffects())),
         draws_content_(draws_content),
@@ -172,10 +169,6 @@
         is_tombstone_(false),
         known_to_be_opaque_is_set_(false),
         known_to_be_opaque_(false) {
-    // |derived_size| must fit in |derived_size_|.
-    // If it doesn't, enlarge |derived_size_| and fix this assert.
-    SECURITY_DCHECK(derived_size == derived_size_);
-    SECURITY_DCHECK(derived_size >= sizeof(*this));
     DCHECK_EQ(client.VisualRectOutsetForRasterEffects(),
               GetRasterEffectOutset());
   }
@@ -214,12 +207,6 @@
 
   Type GetType() const { return static_cast<Type>(type_); }
 
-  // Size of this object in memory, used to move it with memcpy.
-  // This is not sizeof(*this), because it needs to account for the size of
-  // the derived class (i.e. runtime type). Derived classes are expected to
-  // supply this to the DisplayItem constructor.
-  wtf_size_t DerivedSize() const { return derived_size_; }
-
   // The fragment is part of the id, to uniquely identify display items in
   // different fragments for the same client and type.
   wtf_size_t Fragment() const { return fragment_; }
@@ -264,7 +251,7 @@
     // Failure of this DCHECK would cause bad casts in subclasses.
     SECURITY_CHECK(!is_tombstone_);
     return client_ == other.client_ && type_ == other.type_ &&
-           fragment_ == other.fragment_ && derived_size_ == other.derived_size_;
+           fragment_ == other.fragment_;
   }
 
   // True if this DisplayItem is the tombstone/"dead display item" as part of
@@ -281,16 +268,14 @@
 #endif
 
  private:
-  template <typename T, wtf_size_t alignment>
-  friend class ContiguousContainer;
   friend class DisplayItemList;
 
-  // The default DisplayItem constructor is only used by ContiguousContainer::
-  // AppendByMoving() where a tombstone DisplayItem is constructed at the source
-  // location. Only set draws_content_ to false and is_tombstone_ to true,
-  // leaving other fields as-is so that we can get their original values.
-  // |visual_rect_| and |raster_effect_outset_| are special, see
-  // DisplayItemList::AppendByMoving().
+  // The default DisplayItem constructor is only used by DisplayItemList::
+  // AppendByMoving() and ReplaceLastByMoving() where a tombstone DisplayItem is
+  // constructed at the source location. Only set draws_content_ to false and
+  // is_tombstone_ to true, leaving other fields as-is so that we can get their
+  // original values. |visual_rect_| and |raster_effect_outset_| are special,
+  // see DisplayItemList::AppendByMoving().
   DisplayItem() : draws_content_(false), is_tombstone_(true) {}
 
   const DisplayItemClient* client_;
@@ -299,7 +284,6 @@
   static_assert(kTypeLast < (1 << 8),
                 "DisplayItem::Type should fit in uint8_t");
   unsigned type_ : 8;
-  unsigned derived_size_ : 8;  // size of the actual derived class
   unsigned raster_effect_outset_ : 2;
   unsigned draws_content_ : 1;
   unsigned is_cacheable_ : 1;
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_list.cc b/third_party/blink/renderer/platform/graphics/paint/display_item_list.cc
index 2565409..092fdefe 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item_list.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item_list.cc
@@ -9,6 +9,13 @@
 
 namespace blink {
 
+DisplayItemList::~DisplayItemList() {
+  for (auto& item : *this) {
+    (void)item;  // MSVC incorrectly reports this variable as unused.
+    item.~DisplayItem();
+  }
+}
+
 #if DCHECK_IS_ON()
 
 std::unique_ptr<JSONArray> DisplayItemList::DisplayItemsAsJSON(
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_list.h b/third_party/blink/renderer/platform/graphics/paint/display_item_list.h
index fdf99d2..bf33213 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item_list.h
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item_list.h
@@ -5,59 +5,154 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_DISPLAY_ITEM_LIST_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_DISPLAY_ITEM_LIST_H_
 
-#include "base/dcheck_is_on.h"
-#include "third_party/blink/renderer/platform/graphics/contiguous_container.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.h"
-#include "third_party/blink/renderer/platform/wtf/assertions.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/vector.h"
 
 namespace blink {
 
 class JSONArray;
 
-// kDisplayItemAlignment must be a multiple of alignof(derived display item) for
-// each derived display item; the ideal value is the least common multiple.
-// The validity of kDisplayItemAlignment and kMaximumDisplayItemSize are checked
-// in PaintController::CreateAndAppend().
-static constexpr wtf_size_t kDisplayItemAlignment =
-    alignof(ScrollbarDisplayItem);
-static constexpr wtf_size_t kMaximumDisplayItemSize =
-    sizeof(ScrollbarDisplayItem);
-
-// A container for a list of display items.
-class PLATFORM_EXPORT DisplayItemList
-    : public ContiguousContainer<DisplayItem, kDisplayItemAlignment> {
+// A container for a list of display items of various types.
+class PLATFORM_EXPORT DisplayItemList {
  public:
-  static constexpr wtf_size_t kDefaultCapacityInBytes = 512;
+  static constexpr wtf_size_t kDefaultCapacity = 16;
 
-  // Using 0 as the default value to make 0 also fall back to
-  // kDefaultCapacityInBytes.
-  explicit DisplayItemList(wtf_size_t initial_capacity_in_bytes = 0)
-      : ContiguousContainer(kMaximumDisplayItemSize,
-                            initial_capacity_in_bytes
-                                ? initial_capacity_in_bytes
-                                : kDefaultCapacityInBytes) {}
+  // Using 0 as the default value to make 0 also fall back to kDefaultCapacity.
+  // The initial capacity will be allocated when the first item is appended.
+  explicit DisplayItemList(wtf_size_t initial_capacity = 0)
+      : initial_capacity_(initial_capacity ? initial_capacity
+                                           : kDefaultCapacity) {}
+  ~DisplayItemList();
+
+  DisplayItemList(const DisplayItemList&) = delete;
+  DisplayItemList& operator=(const DisplayItemList&) = delete;
+  DisplayItemList(DisplayItemList&&) = delete;
+  DisplayItemList& operator=(DisplayItemList&&) = delete;
+
+  template <class DerivedItemType, typename... Args>
+  DerivedItemType& AllocateAndConstruct(Args&&... args) {
+    static_assert(WTF::IsSubclass<DerivedItemType, DisplayItem>::value,
+                  "Must use subclass of DisplayItem.");
+    static_assert(sizeof(DerivedItemType) <= kMaxItemSize,
+                  "DisplayItem subclass is larger than kMaxItemSize.");
+    static_assert(kAlignment % alignof(DerivedItemType) == 0,
+                  "Derived type requires stronger alignment.");
+    DisplayItem& result = AllocateUninitializedItem();
+    new (&result) DerivedItemType(std::forward<Args>(args)...);
+    return static_cast<DerivedItemType&>(result);
+  }
 
   DisplayItem& AppendByMoving(DisplayItem& item) {
     SECURITY_CHECK(!item.IsTombstone());
-    DisplayItem& result =
-        ContiguousContainer::AppendByMoving(item, item.DerivedSize());
-    SetupTombstone(item, result);
-    return result;
+    DisplayItem& new_item = AllocateUninitializedItem();
+    MoveItem(item, new_item);
+    return new_item;
   }
 
   DisplayItem& ReplaceLastByMoving(DisplayItem& item) {
     SECURITY_CHECK(!item.IsTombstone());
-    DCHECK_EQ(back().DerivedSize(), item.DerivedSize());
-    DisplayItem& result =
-        ContiguousContainer::ReplaceLastByMoving(item, item.DerivedSize());
-    SetupTombstone(item, result);
-    return result;
+    DisplayItem& last = back();
+    last.~DisplayItem();
+    MoveItem(item, last);
+    return last;
+  }
+
+ private:
+  // Declares itself as a forward iterator, but also supports a few more
+  // things. The whole random access iterator interface is a bit much.
+  template <typename BaseIterator, typename ItemType>
+  class IteratorWrapper
+      : public std::iterator<std::forward_iterator_tag, ItemType> {
+    DISALLOW_NEW();
+
+   public:
+    IteratorWrapper() = default;
+    explicit IteratorWrapper(const BaseIterator& it) : it_(it) {}
+
+    bool operator==(const IteratorWrapper& other) const {
+      return it_ == other.it_;
+    }
+    bool operator!=(const IteratorWrapper& other) const {
+      return it_ != other.it_;
+    }
+    bool operator<(const IteratorWrapper& other) const {
+      return it_ < other.it_;
+    }
+    ItemType& operator*() const { return reinterpret_cast<ItemType&>(*it_); }
+    ItemType* operator->() const { return &operator*(); }
+    IteratorWrapper operator+(std::ptrdiff_t n) const {
+      return IteratorWrapper(it_ + n);
+    }
+    IteratorWrapper operator++(int) {
+      IteratorWrapper tmp = *this;
+      ++it_;
+      return tmp;
+    }
+    std::ptrdiff_t operator-(const IteratorWrapper& other) const {
+      return it_ - other.it_;
+    }
+    IteratorWrapper& operator++() {
+      ++it_;
+      return *this;
+    }
+
+   private:
+    BaseIterator it_;
+  };
+
+  // kAlignment must be a multiple of alignof(derived display item) for each
+  // derived display item; the ideal value is the least common multiple.
+  // The validity of kAlignment and kMaxItemSize are checked in
+  // AllocateAndConstruct().
+  static constexpr wtf_size_t kAlignment = alignof(ScrollbarDisplayItem);
+  static constexpr wtf_size_t kMaxItemSize = sizeof(ScrollbarDisplayItem);
+
+  struct ItemSlot {
+    alignas(kAlignment) uint8_t data[kMaxItemSize];
+  };
+  using ItemVector = Vector<ItemSlot>;
+
+ public:
+  using value_type = DisplayItem;
+  using iterator = IteratorWrapper<ItemVector::iterator, DisplayItem>;
+  using const_iterator =
+      IteratorWrapper<ItemVector::const_iterator, const DisplayItem>;
+  iterator begin() { return iterator(items_.begin()); }
+  iterator end() { return iterator(items_.end()); }
+  const_iterator begin() const { return const_iterator(items_.begin()); }
+  const_iterator end() const { return const_iterator(items_.end()); }
+
+  DisplayItem& front() { return *begin(); }
+  const DisplayItem& front() const { return *begin(); }
+  DisplayItem& back() {
+    DCHECK(size());
+    return (*this)[size() - 1];
+  }
+  const DisplayItem& back() const {
+    DCHECK(size());
+    return (*this)[size() - 1];
+  }
+
+  DisplayItem& operator[](wtf_size_t index) { return *(begin() + index); }
+  const DisplayItem& operator[](wtf_size_t index) const {
+    return *(begin() + index);
+  }
+
+  wtf_size_t size() const { return items_.size(); }
+  bool IsEmpty() const { return !size(); }
+
+  size_t MemoryUsageInBytes() const {
+    return sizeof(*this) + items_.CapacityInBytes();
   }
 
   // Useful for iterating with a range-based for loop.
   template <typename Iterator>
   class Range {
+    DISALLOW_NEW();
+
    public:
     Range(const Iterator& begin, const Iterator& end)
         : begin_(begin), end_(end) {}
@@ -103,10 +198,20 @@
 #endif  // DCHECK_IS_ON()
 
  private:
-  // Called by AppendByMoving() and ReplaceLastByMoving() which created a
-  // tombstone/"dead display item" that can be safely destructed but should
-  // never be used except for debugging and raster invalidation.
-  void SetupTombstone(DisplayItem& item, const DisplayItem& new_item) {
+  DisplayItem& AllocateUninitializedItem() {
+    if (items_.IsEmpty())
+      items_.ReserveCapacity(initial_capacity_);
+    items_.emplace_back();
+    return reinterpret_cast<DisplayItem&>(items_.back());
+  }
+
+  void MoveItem(DisplayItem& item, DisplayItem& new_item) {
+    memcpy(static_cast<void*>(&new_item), static_cast<void*>(&item),
+           kMaxItemSize);
+
+    // Created a tombstone/"dead display item" that can be safely destructed but
+    // should never be used except for debugging and raster invalidation.
+    new (&item) DisplayItem;
     DCHECK(item.IsTombstone());
     // We need |visual_rect_| and |outset_for_raster_effects_| of the old
     // display item for raster invalidation. Also, the fields that make up the
@@ -121,6 +226,9 @@
     item.visual_rect_ = new_item.visual_rect_;
     item.raster_effect_outset_ = new_item.raster_effect_outset_;
   }
+
+  ItemVector items_;
+  wtf_size_t initial_capacity_;
 };
 
 using DisplayItemIterator = DisplayItemList::const_iterator;
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
index 7aabddc..2f4a24d9 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h
@@ -67,7 +67,6 @@
                                               sk_sp<const PaintRecord> record)
     : DisplayItem(client,
                   type,
-                  sizeof(*this),
                   visual_rect,
                   /* draws_content*/ record && record->size()),
       record_(DrawsContent() ? std::move(record) : nullptr) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
index 52c2622..5b64e82 100644
--- a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
@@ -22,7 +22,6 @@
     const IntPoint& offset)
     : DisplayItem(client,
                   type,
-                  sizeof(*this),
                   IntRect(offset, IntSize(layer->bounds()))),
       layer_(std::move(layer)) {
   DCHECK(IsForeignLayerType(type));
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
index f2250a8..1f23d41a 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
@@ -27,11 +27,10 @@
   USING_FAST_MALLOC(PaintArtifact);
 
  public:
-  explicit PaintArtifact(
-      wtf_size_t initial_display_item_list_capacity_in_bytes = 0,
-      wtf_size_t initial_paint_chunks_capacity_in_elements = 0)
-      : display_item_list_(initial_display_item_list_capacity_in_bytes) {
-    chunks_.ReserveInitialCapacity(initial_paint_chunks_capacity_in_elements);
+  explicit PaintArtifact(wtf_size_t initial_display_item_list_capacity = 0,
+                         wtf_size_t initial_paint_chunks_capacity = 0)
+      : display_item_list_(initial_display_item_list_capacity) {
+    chunks_.ReserveInitialCapacity(initial_paint_chunks_capacity);
   }
 
   PaintArtifact(const PaintArtifact& other) = delete;
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index adcb19e..0bb20dc 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -556,7 +556,7 @@
   current_paint_artifact_ = std::move(new_paint_artifact_);
   if (usage_ == kMultiplePaints) {
     new_paint_artifact_ = base::MakeRefCounted<PaintArtifact>(
-        current_paint_artifact_->GetDisplayItemList().UsedCapacityInBytes(),
+        current_paint_artifact_->GetDisplayItemList().size(),
         current_paint_artifact_->PaintChunks().size());
     paint_chunker_.ResetChunks(&new_paint_artifact_->PaintChunks());
   } else {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
index b5e7cec1..c14f15c 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -15,7 +15,6 @@
 #include "cc/input/layer_selection_bound.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/geometry/layout_point.h"
-#include "third_party/blink/renderer/platform/graphics/contiguous_container.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item_list.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_artifact.h"
@@ -132,15 +131,6 @@
 
   template <typename DisplayItemClass, typename... Args>
   void CreateAndAppend(Args&&... args) {
-    static_assert(WTF::IsSubclass<DisplayItemClass, DisplayItem>::value,
-                  "Can only createAndAppend subclasses of DisplayItem.");
-    static_assert(
-        sizeof(DisplayItemClass) <= kMaximumDisplayItemSize,
-        "DisplayItem subclass is larger than kMaximumDisplayItemSize.");
-    static_assert(kDisplayItemAlignment % alignof(DisplayItemClass) == 0,
-                  "DisplayItem subclass alignment is not a factor of "
-                  "kDisplayItemAlignment.");
-
     DisplayItemClass& display_item =
         new_paint_artifact_->GetDisplayItemList()
             .AllocateAndConstruct<DisplayItemClass>(
diff --git a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
index 3995ac8e..a469c08 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
@@ -26,7 +26,6 @@
     CompositorElementId element_id)
     : DisplayItem(client,
                   type,
-                  sizeof(*this),
                   visual_rect,
                   /*draws_content*/ true),
       data_(new Data{std::move(scrollbar), scroll_translation, element_id}) {
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
index 6958ec85..dcfbf08 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
@@ -11,6 +11,8 @@
 #include "base/threading/thread_checker.h"
 #include "base/trace_event/trace_event.h"
 #include "media/base/audio_parameters.h"
+#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
@@ -48,6 +50,9 @@
     DCHECK(!base::Contains(consumers_, consumer));
     DCHECK(!base::Contains(pending_consumers_, consumer));
     pending_consumers_.push_back(consumer);
+    SendLogMessage(
+        String::Format("%s => (number of consumer: active=%u, pending=%u)",
+                       __func__, consumers_.size(), pending_consumers_.size()));
   }
 
   // Stop delivering audio to |consumer|. Returns true if |consumer| was the
@@ -68,6 +73,9 @@
       if (it != pending_consumers_.end())
         pending_consumers_.erase(it);
     }
+    SendLogMessage(
+        String::Format("%s => (number of consumers: active=%u, pending=%u)",
+                       __func__, consumers_.size(), pending_consumers_.size()));
     return had_consumers && consumers_.IsEmpty() &&
            pending_consumers_.IsEmpty();
   }
@@ -93,6 +101,8 @@
       base::AutoLock auto_params_lock(params_lock_);
       if (params_.Equals(params))
         return;
+      SendLogMessage(String::Format("%s({params=[%s]})", __func__,
+                                    params.AsHumanReadableString().c_str()));
       params_ = params;
     }
     pending_consumers_.AppendRange(consumers_.begin(), consumers_.end());
@@ -117,6 +127,8 @@
       consumers_.AppendRange(pending_consumers_.begin(),
                              pending_consumers_.end());
       pending_consumers_.clear();
+      SendLogMessage(String::Format("%s => (number of active consumers=%u)",
+                                    __func__, consumers_.size()));
     }
 
     // Deliver the audio data to each consumer.
@@ -137,6 +149,13 @@
   }
 
  private:
+  void SendLogMessage(const WTF::String& message) {
+    WebRtcLogMessage(String::Format("MSAD::%s [this=0x%" PRIXPTR "]",
+                                    message.Utf8().c_str(),
+                                    reinterpret_cast<uintptr_t>(this))
+                         .Utf8());
+  }
+
   // In debug builds, check that all methods that could cause object graph or
   // data flow changes are being called on the main thread.
   THREAD_CHECKER(thread_checker_);
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.cc
index 92a8fdc..59a2c46 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/single_thread_task_runner.h"
@@ -22,14 +23,6 @@
 
 namespace blink {
 
-namespace {
-
-void SendLogMessage(const std::string& message) {
-  blink::WebRtcLogMessage("MSAS::" + message);
-}
-
-}  // namespace
-
 // TODO(https://crbug.com/638081):
 // Like in ProcessedLocalAudioSource::GetBufferSize(), we should re-evaluate
 // whether Android needs special treatment here.
@@ -53,9 +46,10 @@
       disable_local_echo_(disable_local_echo),
       is_stopped_(false),
       task_runner_(std::move(task_runner)) {
-  SendLogMessage(base::StringPrintf(
-      "MediaStreamAudioSource([this=%p] {is_local_source=%s})", this,
-      (is_local_source ? "local" : "remote")));
+  LogMessage(
+      base::StringPrintf("%s({is_local_source=%s}, {disable_local_echo=%s})",
+                         __func__, is_local_source ? "local" : "remote",
+                         disable_local_echo ? "true" : "false"));
 }
 
 MediaStreamAudioSource::MediaStreamAudioSource(
@@ -67,8 +61,6 @@
 
 MediaStreamAudioSource::~MediaStreamAudioSource() {
   DCHECK(task_runner_->BelongsToCurrentThread());
-  SendLogMessage(
-      base::StringPrintf("~MediaStreamAudioSource([this=%p])", this));
 }
 
 // static
@@ -83,8 +75,6 @@
 bool MediaStreamAudioSource::ConnectToTrack(MediaStreamComponent* component) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(component);
-  SendLogMessage(base::StringPrintf("ConnectToTrack({track_id=%s})",
-                                    component->Id().Utf8().c_str()));
 
   // Sanity-check that there is not already a MediaStreamAudioTrack instance
   // associated with |component|.
@@ -94,6 +84,9 @@
     return false;
   }
 
+  LogMessage(base::StringPrintf("%s(track=%s)", __func__,
+                                component->ToString().Utf8().c_str()));
+
   // Unless the source has already been permanently stopped, ensure it is
   // started. If the source cannot start, the new MediaStreamAudioTrack will be
   // initialized to the stopped/ended state.
@@ -118,9 +111,11 @@
 
   track->Start(WTF::Bind(&MediaStreamAudioSource::StopAudioDeliveryTo,
                          weak_factory_.GetWeakPtr(), WTF::Unretained(track)));
-  DVLOG(1) << "Adding MediaStreamAudioTrack@" << track
-           << " as a consumer of MediaStreamAudioSource@" << this << '.';
   deliverer_.AddConsumer(track);
+  LogMessage(
+      base::StringPrintf("%s => (added new MediaStreamAudioTrack as consumer, "
+                         "total number of consumers=%d)",
+                         __func__, NumConsumers()));
   return true;
 }
 
@@ -133,6 +128,22 @@
   return device().matched_output_device_id.has_value();
 }
 
+bool MediaStreamAudioSource::AllTracksAreDisabled() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  unsigned int num_disabled_tracks = 0;
+  Vector<MediaStreamAudioTrack*> audio_tracks;
+  deliverer_.GetConsumerList(&audio_tracks);
+  for (MediaStreamAudioTrack* track : audio_tracks) {
+    if (!track->IsEnabled())
+      ++num_disabled_tracks;
+  }
+  LogMessage(base::StringPrintf("%s => (%u of %u tracks are disabled))",
+                                __func__, num_disabled_tracks,
+                                audio_tracks.size()));
+  return (num_disabled_tracks == audio_tracks.size());
+}
+
 void* MediaStreamAudioSource::GetClassIdentifier() const {
   return nullptr;
 }
@@ -177,8 +188,9 @@
 std::unique_ptr<MediaStreamAudioTrack>
 MediaStreamAudioSource::CreateMediaStreamAudioTrack(const std::string& id) {
   DCHECK(task_runner_->BelongsToCurrentThread());
-  SendLogMessage(
-      base::StringPrintf("CreateMediaStreamAudioTrack({id=%s})", id.c_str()));
+  LogMessage(base::StringPrintf("%s({id=%s}, {is_local_source=%s})", __func__,
+                                id.c_str(),
+                                is_local_source() ? "local" : "remote"));
   return std::make_unique<MediaStreamAudioTrack>(is_local_source());
 }
 
@@ -201,8 +213,8 @@
 }
 
 void MediaStreamAudioSource::SetFormat(const media::AudioParameters& params) {
-  SendLogMessage(base::StringPrintf(
-      "SetFormat([this=%p] {params=[%s]}, {old_params=[%s]})", this,
+  LogMessage(base::StringPrintf(
+      "%s({params=[%s]}, {old_params=[%s]})", __func__,
       params.AsHumanReadableString().c_str(),
       deliverer_.GetAudioParameters().AsHumanReadableString().c_str()));
   deliverer_.OnSetFormat(params);
@@ -216,27 +228,30 @@
 
 void MediaStreamAudioSource::DoStopSource() {
   DCHECK(task_runner_->BelongsToCurrentThread());
+  LogMessage(base::StringPrintf("%s()", __func__));
   EnsureSourceIsStopped();
   is_stopped_ = true;
 }
 
 void MediaStreamAudioSource::StopAudioDeliveryTo(MediaStreamAudioTrack* track) {
   DCHECK(task_runner_->BelongsToCurrentThread());
-  SendLogMessage(base::StringPrintf("StopAudioDeliveryTo([this=%p])", this));
-
   const bool did_remove_last_track = deliverer_.RemoveConsumer(track);
-  DVLOG(1) << "Removed MediaStreamAudioTrack@" << track
-           << " as a consumer of MediaStreamAudioSource@" << this << '.';
+  LogMessage(
+      base::StringPrintf("%s => (removed MediaStreamAudioTrack as consumer, "
+                         "total number of consumers=%u)",
+                         __func__, NumConsumers()));
 
   // The W3C spec requires a source automatically stop when the last track is
   // stopped.
-  if (!is_stopped_ && did_remove_last_track)
+  if (!is_stopped_ && did_remove_last_track) {
+    LogMessage(base::StringPrintf("%s => (last track removed, stopping source)",
+                                  __func__));
     WebPlatformMediaStreamSource::StopSource();
+  }
 }
 
 void MediaStreamAudioSource::StopSourceOnError(const std::string& why) {
-  SendLogMessage(base::StringPrintf("StopSourceOnError([this=%p] {why=%s})",
-                                    this, why.c_str()));
+  LogMessage(base::StringPrintf("%s({why=%s})", __func__, why.c_str()));
   // Stop source when error occurs.
   PostCrossThreadTask(
       *task_runner_, FROM_HERE,
@@ -245,8 +260,8 @@
 }
 
 void MediaStreamAudioSource::SetMutedState(bool muted_state) {
-  SendLogMessage(base::StringPrintf("SetMutedState([this=%p] {muted_state=%s})",
-                                    this, (muted_state ? "true" : "false")));
+  LogMessage(base::StringPrintf("%s({muted_state=%s})", __func__,
+                                muted_state ? "true" : "false"));
   PostCrossThreadTask(
       *task_runner_, FROM_HERE,
       WTF::CrossThreadBindOnce(&WebPlatformMediaStreamSource::SetSourceMuted,
@@ -261,4 +276,17 @@
   return deliverer_.NumPreferredChannels();
 }
 
+int MediaStreamAudioSource::NumConsumers() const {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  Vector<MediaStreamAudioTrack*> audio_tracks;
+  deliverer_.GetConsumerList(&audio_tracks);
+  return static_cast<int>(audio_tracks.size());
+}
+
+void MediaStreamAudioSource::LogMessage(const std::string& message) {
+  blink::WebRtcLogMessage(
+      base::StringPrintf("MSAS::%s [this=0x%" PRIXPTR "]", message.c_str(),
+                         reinterpret_cast<uintptr_t>(this)));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h
index 245d970..8aa6c251 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h
@@ -101,6 +101,9 @@
   bool disable_local_echo() const { return disable_local_echo_; }
   bool RenderToAssociatedSinkEnabled() const;
 
+  // Checks all tracks acting as consumers and returns true if all are disabled.
+  bool AllTracksAreDisabled();
+
   // Returns a unique class identifier. Some subclasses override and use this
   // method to provide safe down-casting to their type.
   virtual void* GetClassIdentifier() const;
@@ -188,6 +191,11 @@
   // this.
   void StopAudioDeliveryTo(MediaStreamAudioTrack* track);
 
+  // Number of MediaStreamAudioTracks added as consumers.
+  int NumConsumers() const;
+
+  void LogMessage(const std::string& message);
+
   // True if the source of audio is a local device. False if the source is
   // remote (e.g., streamed-in from a server).
   const bool is_local_source_;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
index 0cd782e..13cd2570 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
 
+#include <string>
 #include <utility>
 
 #include "base/check_op.h"
@@ -89,6 +90,11 @@
     sink->OnEnabledChanged(enabled);
 }
 
+bool MediaStreamAudioTrack::IsEnabled() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return is_enabled_;
+}
+
 void MediaStreamAudioTrack::SetContentHint(
     WebMediaStreamTrack::ContentHintType content_hint) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
index df808db..d3492bf 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h
@@ -74,6 +74,8 @@
   // this track.
   int NumPreferredChannels() const;
 
+  bool IsEnabled() const;
+
   // Returns a unique class identifier. Some subclasses override and use this
   // method to provide safe down-casting to their type.
   virtual void* GetClassIdentifier() const;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
index 93b64ad..bbfa611c 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
@@ -133,6 +133,12 @@
   web_audio_source_provider_->ProvideInput(web_audio_data_, frames_to_process);
 }
 
+String MediaStreamComponent::ToString() const {
+  return String::Format(
+      "[id: %s, unique_id: %d, enabled: %s, muted=%s]", Id().Utf8().c_str(),
+      UniqueId(), Enabled() ? "true" : "false", Muted() ? "true" : "false");
+}
+
 void MediaStreamComponent::Trace(Visitor* visitor) const {
   visitor->Trace(source_);
 }
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.h b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
index 9ed0723..81f8b50 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
@@ -103,6 +103,8 @@
   }
   void GetSettings(MediaStreamTrackPlatform::Settings&);
 
+  String ToString() const;
+
   void Trace(Visitor*) const;
 
  private:
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
index 638ce5d..16b5c1e3 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
@@ -167,7 +167,7 @@
   SendLogMessage(
       String::Format(
           "MediaStreamSource({id=%s}, {type=%s}, {name=%s}, {remote=%d}, "
-          "{ready_state=%s}",
+          "{ready_state=%s})",
           id.Utf8().c_str(), StreamTypeToString(type), name.Utf8().c_str(),
           remote, ReadyStateToString(ready_state))
           .Utf8());
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 8432695..84fe3f7 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1187,8 +1187,7 @@
     },
     {
       name: "LayoutNGReplaced",
-      depends_on: ["LayoutNG"],
-      status: "stable",
+      depends_on: ["LayoutNG"]
     },
     {
       name: "LayoutNGTable",
@@ -1231,10 +1230,6 @@
       "implied_by": ["DeviceAttributes", "ManagedConfiguration"],
     },
     {
-      name:"ManualSlotting",
-      status:"stable",
-    },
-    {
       name: "MathMLCore",
       status:"experimental",
       depends_on: ["LayoutNG"],
diff --git a/third_party/blink/renderer/platform/widget/compositing/DEPS b/third_party/blink/renderer/platform/widget/compositing/DEPS
index 8ea5498..b0ba92a 100644
--- a/third_party/blink/renderer/platform/widget/compositing/DEPS
+++ b/third_party/blink/renderer/platform/widget/compositing/DEPS
@@ -5,6 +5,7 @@
     "+cc",
     "+components/viz/client",
     "+components/viz/common",
+    "+services/metrics/public/cpp/mojo_ukm_recorder.h",
 ]
 
 specific_include_rules = {
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
index 17bf278..c711dca 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
@@ -37,6 +37,9 @@
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/quads/compositor_frame_metadata.h"
+#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+#include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h"
@@ -49,6 +52,27 @@
 
 namespace blink {
 
+namespace {
+// This factory is used to defer binding of the InterfacePtr to the compositor
+// thread.
+class UkmRecorderFactoryImpl : public cc::UkmRecorderFactory {
+ public:
+  UkmRecorderFactoryImpl() = default;
+  ~UkmRecorderFactoryImpl() override = default;
+
+  // This method gets called on the compositor thread.
+  std::unique_ptr<ukm::UkmRecorder> CreateRecorder() override {
+    mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
+
+    // Calling these methods on the compositor thread are thread safe.
+    Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
+        recorder.InitWithNewPipeAndPassReceiver());
+    return std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
+  }
+};
+
+}  // namespace
+
 LayerTreeView::LayerTreeView(LayerTreeViewDelegate* delegate,
                              scheduler::WebThreadScheduler* scheduler)
     : web_main_thread_scheduler_(scheduler),
@@ -64,7 +88,6 @@
     scoped_refptr<base::SingleThreadTaskRunner> main_thread,
     scoped_refptr<base::SingleThreadTaskRunner> compositor_thread,
     cc::TaskGraphRunner* task_graph_runner,
-    std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
     gfx::RenderingPipeline* main_thread_pipeline,
     gfx::RenderingPipeline* compositor_thread_pipeline) {
   DCHECK(delegate_);
@@ -78,7 +101,7 @@
   params.main_task_runner = std::move(main_thread);
   params.mutator_host = animation_host_.get();
   params.dark_mode_filter = dark_mode_filter_.get();
-  params.ukm_recorder_factory = std::move(ukm_recorder_factory);
+  params.ukm_recorder_factory = std::make_unique<UkmRecorderFactoryImpl>();
   params.main_thread_pipeline = main_thread_pipeline;
   params.compositor_thread_pipeline = compositor_thread_pipeline;
   if (base::ThreadPoolInstance::Get()) {
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
index 56533e9..e49efed 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
@@ -29,7 +29,6 @@
 class LayerTreeSettings;
 class RenderFrameMetadataObserver;
 class TaskGraphRunner;
-class UkmRecorderFactory;
 }  // namespace cc
 
 namespace gfx {
@@ -61,7 +60,6 @@
                   scoped_refptr<base::SingleThreadTaskRunner> main_thread,
                   scoped_refptr<base::SingleThreadTaskRunner> compositor_thread,
                   cc::TaskGraphRunner* task_graph_runner,
-                  std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
                   gfx::RenderingPipeline* main_thread_pipeline,
                   gfx::RenderingPipeline* compositor_thread_pipeline);
 
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_unittest.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_unittest.cc
index e91ed7b1..193b584 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_unittest.cc
@@ -211,7 +211,6 @@
     layer_tree_view_.Initialize(
         settings, blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
         /*compositor_thread=*/nullptr, &test_task_graph_runner_,
-        std::make_unique<cc::TestUkmRecorderFactory>(),
         /*main_thread_pipeline=*/nullptr,
         /*compositor_thread_pipeline=*/nullptr);
   }
@@ -334,7 +333,6 @@
       cc::LayerTreeSettings(),
       blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
       /*compositor_thread=*/nullptr, &test_task_graph_runner,
-      std::make_unique<cc::TestUkmRecorderFactory>(),
       /*main_thread_pipeline=*/nullptr,
       /*compositor_thread_pipeline=*/nullptr);
 
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 8b22ff5..dc6ae1a 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -176,7 +176,6 @@
     cc::TaskGraphRunner* task_graph_runner,
     bool for_child_local_root_frame,
     const ScreenInfos& screen_infos,
-    std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
     const cc::LayerTreeSettings* settings,
     base::WeakPtr<mojom::blink::FrameWidgetInputHandler>
         frame_widget_input_handler,
@@ -207,8 +206,7 @@
       compositing_thread_scheduler
           ? compositing_thread_scheduler->DefaultTaskRunner()
           : nullptr,
-      task_graph_runner, std::move(ukm_recorder_factory), main_thread_pipeline,
-      compositor_thread_pipeline);
+      task_graph_runner, main_thread_pipeline, compositor_thread_pipeline);
 
   FrameWidget* frame_widget = client_->FrameWidget();
 
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index fec94762..0e6f7f5 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -32,7 +32,6 @@
 class LayerTreeHost;
 class LayerTreeSettings;
 class TaskGraphRunner;
-class UkmRecorderFactory;
 }  // namespace cc
 
 namespace gfx {
@@ -92,7 +91,6 @@
       cc::TaskGraphRunner* task_graph_runner,
       bool for_child_local_root_frame,
       const ScreenInfos& screen_infos,
-      std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory,
       const cc::LayerTreeSettings* settings,
       base::WeakPtr<mojom::blink::FrameWidgetInputHandler>
           frame_widget_input_handler,
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_metadata_builder.py b/third_party/blink/tools/blinkpy/w3c/wpt_metadata_builder.py
index 66e9c49..0373e86 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_metadata_builder.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_metadata_builder.py
@@ -258,8 +258,9 @@
             if self.process_baselines:
                 test_baseline = self.port.expected_text(test_name)
                 if test_baseline:
-                    self._handle_test_with_baseline(test_name, test_baseline,
-                                                    tests_needing_metadata)
+                    self._handle_test_with_baseline(
+                        test_name, test_baseline.decode('utf8', 'replace'),
+                        tests_needing_metadata)
 
             # Next check for expectations, which could overwrite baselines
             expectation_line = self.expectations.get_expectations(test_name)
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c74ed46..8cd595ad 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -562,6 +562,7 @@
 crbug.com/711807 external/wpt/css/CSS2/normal-flow/max-width-applies-to-006.xht [ Failure ]
 crbug.com/711807 external/wpt/css/CSS2/normal-flow/replaced-intrinsic-001.xht [ Failure ]
 crbug.com/711807 external/wpt/css/CSS2/normal-flow/replaced-intrinsic-002.xht [ Failure ]
+crbug.com/1114280 external/wpt/css/CSS2/normal-flow/replaced-intrinsic-003.xht [ Failure ]
 
 #### external/wpt/css/css-position
 crbug.com/752022 external/wpt/css/css-position/sticky/position-sticky-offset-overflow.html [ Failure ]
@@ -569,6 +570,18 @@
 #### external/wpt/css/css-sizing
 crbug.com/1164135 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html [ Failure ]
 crbug.com/1164135 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html [ Failure ]
+crbug.com/1114280 external/wpt/css/css-sizing/aspect-ratio/replaced-element-034.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html [ Failure ]
+crbug.com/1114280 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-001.html [ Failure ]
+crbug.com/1114280 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-002.html [ Failure ]
+crbug.com/1114280 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-003.html [ Failure ]
 
 crbug.com/1008951 external/wpt/css/css-text-decor/text-decoration-subelements-002.html [ Failure ]
 crbug.com/1008951 external/wpt/css/css-text-decor/text-decoration-subelements-003.html [ Failure ]
@@ -821,9 +834,8 @@
 crbug.com/470421 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-021.html [ Failure ]
 crbug.com/470421 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-022.html [ Failure ]
 
-# Bad test expectations, but we aren't sure if web compatible yet.
-crbug.com/1114280 external/wpt/css/css-flexbox/flexbox-min-width-auto-005.html [ Failure ]
-crbug.com/1114280 external/wpt/css/css-flexbox/flexbox-min-width-auto-006.html [ Failure ]
+# Correct min/max content sizes for a replaced element wont be supported until ReplacedNG.
+crbug.com/1114280 external/wpt/css/css-flexbox/aspect-ratio-intrinsic-size-006.html [ Failure ]
 
 # [css-lists]
 crbug.com/1123457 external/wpt/css/css-lists/counter-list-item-2.html [ Failure ]
@@ -1284,6 +1296,8 @@
 crbug.com/929098 external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Failure ]
 crbug.com/929098 virtual/dark-color-scheme/external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Pass ]
 
+crbug.com/1205953 external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
+
 # color() function not implemented
 crbug.com/1068610 external/wpt/css/css-color/predefined-008.html [ Failure ]
 crbug.com/1068610 external/wpt/css/css-color/predefined-005.html [ Failure ]
@@ -2510,12 +2524,6 @@
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 external/wpt/css/css-will-change/will-change-stacking-context-mask-1.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-will-change/will-change-fixpos-cb-transform-style-1.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
-crbug.com/626703 [ Mac10.12 ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
-crbug.com/626703 [ Mac10.13 ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
-crbug.com/626703 [ Mac10.15 ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
-crbug.com/626703 [ Mac11.0 ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-will-change/will-change-fixpos-cb-contain-1.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-004.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-002.html [ Failure ]
@@ -4131,7 +4139,7 @@
 crbug.com/1045599 external/wpt/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html [ Failure ]
 crbug.com/1045599 external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html [ Failure ]
 crbug.com/1045599 external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html [ Failure ]
-crbug.com/1045599 external/wpt/css/css-grid/grid-items/aspect-ratio-004.html [ Failure ]
+crbug.com/1114280 external/wpt/css/css-grid/grid-items/aspect-ratio-004.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-overflow-padding-001.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-overflow-padding-002.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-001.html [ Failure ]
@@ -4229,7 +4237,6 @@
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html [ Pass ]
-virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/aspect-ratio-004.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-auto-margin-and-replaced-item-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-002.html [ Pass ]
@@ -4814,6 +4821,13 @@
 crbug.com/619427 [ Mac ] fast/overflow/overflow-height-float-not-removed-crash3.html [ Pass Failure ]
 
 # [css-ui] Imported tests from W3C suite.
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-014.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-015.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-016.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-018.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-019.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-024.html [ Failure ]
+crbug.com/669473 external/wpt/css/css-ui/box-sizing-025.html [ Failure ]
 crbug.com/669473 external/wpt/css/css-ui/outline-005.html [ Failure ]
 crbug.com/669473 external/wpt/css/css-ui/outline-006.html [ Failure ]
 
@@ -6040,9 +6054,9 @@
 crbug.com/1048149 crbug.com/1050121 [ Mac ] fast/forms/month/month-picker-appearance-zoom150.html [ Pass Crash ]
 
 # SwANGLE issues
-crbug.com/1204234 [ Linux ] css3/blending/background-blend-mode-single-accelerated-element.html [ Failure ]
-crbug.com/1204234 [ Linux ] external/wpt/webxr/light-estimation/xrWebGLBinding_getReflectionCubeMap.https.html [ Failure ]
-crbug.com/1204234 [ Linux ] external/wpt/webxr/xrWebGLLayer_opaque_framebuffer_stencil.https.html [ Failure ]
+crbug.com/1204234 css3/blending/background-blend-mode-single-accelerated-element.html [ Failure ]
+crbug.com/1204234 external/wpt/webxr/light-estimation/xrWebGLBinding_getReflectionCubeMap.https.html [ Failure ]
+crbug.com/1204234 external/wpt/webxr/xrWebGLLayer_opaque_framebuffer_stencil.https.html [ Failure ]
 
 # Upcoming DevTools change
 crbug.com/1006759 http/tests/devtools/profiler/cpu-profiler-save-load.js [ Pass Failure Timeout ]
@@ -7098,4 +7112,3 @@
 crbug.com/1205780 [ Mac10.15 ] external/wpt/css/mediaqueries/test_media_queries.html [ Failure Pass ]
 crbug.com/1205796 [ Mac10.12 ] external/wpt/html/dom/idlharness.https.html?include=HTML.* [ Failure ]
 crbug.com/1205796 [ Mac10.14 ] external/wpt/html/dom/idlharness.https.html?include=HTML.* [ Failure ]
-crbug.com/1205801 [ Mac10.14 ] external/wpt/css/css-will-change/will-change-fixpos-cb-position-1.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/reporting/support/set-cookie.py b/third_party/blink/web_tests/external/wpt/content-security-policy/reporting/support/set-cookie.py
index 38325f06..db747bc 100644
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/reporting/support/set-cookie.py
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/reporting/support/set-cookie.py
@@ -1,3 +1,5 @@
+from datetime import date
+
 def main(request, response):
     """
     Returns cookie name and path from query params in a Set-Cookie header.
@@ -11,7 +13,7 @@
     >
     < HTTP/1.1 200 OK
     < Content-Type: application/json
-    < Set-Cookie: match-slash=1; Path=/; Expires=Wed, 09 Jun 2021 10:18:14 GMT
+    < Set-Cookie: match-slash=1; Path=/; Expires=09 Jun 2021 10:18:14 GMT
     < Server: BaseHTTP/0.3 Python/2.7.12
     < Date: Tue, 04 Oct 2016 18:16:06 GMT
     < Content-Length: 80
@@ -19,7 +21,8 @@
 
     name = request.GET[b'name']
     path = request.GET[b'path']
-    cookie = b"%s=1; Path=%s; Expires=Wed, 09 Jun 2021 10:18:14 GMT" % (name, path)
+    expiry_year = date.today().year + 1
+    cookie = b"%s=1; Path=%s; Expires=09 Jun %d 10:18:14 GMT" % (name, path, expiry_year)
 
     headers = [
         (b"Content-Type", b"application/json"),
diff --git a/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py b/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
index 38325f06..db747bc 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
+++ b/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
@@ -1,3 +1,5 @@
+from datetime import date
+
 def main(request, response):
     """
     Returns cookie name and path from query params in a Set-Cookie header.
@@ -11,7 +13,7 @@
     >
     < HTTP/1.1 200 OK
     < Content-Type: application/json
-    < Set-Cookie: match-slash=1; Path=/; Expires=Wed, 09 Jun 2021 10:18:14 GMT
+    < Set-Cookie: match-slash=1; Path=/; Expires=09 Jun 2021 10:18:14 GMT
     < Server: BaseHTTP/0.3 Python/2.7.12
     < Date: Tue, 04 Oct 2016 18:16:06 GMT
     < Content-Length: 80
@@ -19,7 +21,8 @@
 
     name = request.GET[b'name']
     path = request.GET[b'path']
-    cookie = b"%s=1; Path=%s; Expires=Wed, 09 Jun 2021 10:18:14 GMT" % (name, path)
+    expiry_year = date.today().year + 1
+    cookie = b"%s=1; Path=%s; Expires=09 Jun %d 10:18:14 GMT" % (name, path, expiry_year)
 
     headers = [
         (b"Content-Type", b"application/json"),
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-parser-interaction.tentative.html b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-parser-interaction.tentative.html
new file mode 100644
index 0000000..473131e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-parser-interaction.tentative.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Declarative Shadow DOM</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://github.com/whatwg/dom/issues/831">
+<link rel="help" href="https://crbug.com/1203645">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/helpers.js"></script>
+
+
+This test should not crash, and there should be two lines of text visible below.
+<x-1>
+    <template shadowroot="open">
+        <style>
+            @import 'non-existent.css';
+        </style>
+        <slot></slot>
+    </template>
+    <p>Line 1</p>
+</x-1>
+
+<x-2>
+    <template shadowroot="open">
+        <slot></slot>
+    </template>
+    <p>Line 2</p>
+</x-2>
+
+<script>
+  window.onload = function() {
+    const x1 = document.querySelector('x-1');
+    const x2 = document.querySelector('x-2');
+    test(() => {
+      assert_true(!!x1);
+      assert_true(!!x2);
+    }, 'Declarative Shadow DOM: Test for crashes and improper parsing');
+  }
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/svg/interact/scripted/tabindex-focus-flag-expected.txt b/third_party/blink/web_tests/external/wpt/svg/interact/scripted/tabindex-focus-flag-expected.txt
index 5ca66db..7013722 100644
--- a/third_party/blink/web_tests/external/wpt/svg/interact/scripted/tabindex-focus-flag-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/svg/interact/scripted/tabindex-focus-flag-expected.txt
@@ -7,9 +7,9 @@
 PASS a[href] should be focusable.
 FAIL a[focusable=false] should not be focusable. assert_not_equals: got disallowed value Element node <a href="" focusable="false"></a>
 FAIL iframe should be focusable. assert_equals: expected Element node <iframe src="resources/blank.htm"></iframe> but got Element node <a href="" focusable="false"></a>
-FAIL audio[controls] should be focusable. assert_equals: expected Element node <audio controls="controls"></audio> but got Element node <a href="" focusable="false"></a>
-FAIL video[controls] should be focusable. assert_equals: expected Element node <video controls="controls"></video> but got Element node <a href="" focusable="false"></a>
-FAIL canvas[tabindex] should be focusable. assert_equals: expected Element node <canvas tabindex="0"></canvas> but got Element node <a href="" focusable="false"></a>
+PASS audio[controls] should be focusable.
+PASS video[controls] should be focusable.
+FAIL canvas[tabindex] should be focusable. assert_equals: expected Element node <canvas tabindex="0"></canvas> but got Element node <video controls="controls"></video>
 PASS circle[tabindex] should be focusable.
 PASS ellipse[tabindex] should be focusable.
 PASS foreignObject[tabindex] should be focusable.
diff --git a/third_party/blink/web_tests/fast/canvas-api/fallback-content.html b/third_party/blink/web_tests/fast/canvas-api/fallback-content.html
index 9dff9a65..2fdb90c 100644
--- a/third_party/blink/web_tests/fast/canvas-api/fallback-content.html
+++ b/third_party/blink/web_tests/fast/canvas-api/fallback-content.html
@@ -44,14 +44,14 @@
 function checkFocusable(id) {
     element = document.getElementById(id);
     element.focus();
-    assert_true(document.activeElement == element);
+    assert_equals(document.activeElement, element);
 }
 
 function checkNotFocusable(id) {
     previousFocusedElement = document.activeElement;
     element = document.getElementById(id);
     element.focus();
-    assert_true(document.activeElement == previousFocusedElement);
+    assert_equals(document.activeElement, previousFocusedElement);
 }
 
 var focusableTestScenarios = [
diff --git a/third_party/blink/web_tests/fast/replaced/table-percent-width-expected.txt b/third_party/blink/web_tests/fast/replaced/table-percent-width-expected.txt
index 9784532..6f74758 100644
--- a/third_party/blink/web_tests/fast/replaced/table-percent-width-expected.txt
+++ b/third_party/blink/web_tests/fast/replaced/table-percent-width-expected.txt
@@ -16,7 +16,7 @@
 PASS getWidth('img-4') is '102px'
 PASS getHeight('img-4') is '102px'
 PASS getWidth('img-5') is '40px'
-PASS getHeight('img-5') is '42px'
+PASS getHeight('img-5') is '102px'
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/third_party/blink/web_tests/fast/replaced/table-percent-width.html b/third_party/blink/web_tests/fast/replaced/table-percent-width.html
index 8ea77b33..092a1eb 100644
--- a/third_party/blink/web_tests/fast/replaced/table-percent-width.html
+++ b/third_party/blink/web_tests/fast/replaced/table-percent-width.html
@@ -54,7 +54,7 @@
     shouldBe("getWidth('img-4')", "'102px'");
     shouldBe("getHeight('img-4')", "'102px'");
     shouldBe("getWidth('img-5')", "'40px'");
-    shouldBe("getHeight('img-5')", "'42px'");
+    shouldBe("getHeight('img-5')", "'102px'");
 
     isSuccessfullyParsed();
 
diff --git a/third_party/blink/web_tests/platform/linux/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/linux/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
deleted file mode 100644
index d90afb61..0000000
--- a/third_party/blink/web_tests/platform/linux/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/tables/mozilla/bugs/bug137388-2-expected.png b/third_party/blink/web_tests/platform/linux/tables/mozilla/bugs/bug137388-2-expected.png
index 756fdf6f..68ed1dae 100644
--- a/third_party/blink/web_tests/platform/linux/tables/mozilla/bugs/bug137388-2-expected.png
+++ b/third_party/blink/web_tests/platform/linux/tables/mozilla/bugs/bug137388-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
deleted file mode 100644
index 0e9fc25..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png b/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
deleted file mode 100644
index 266c825..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
deleted file mode 100644
index 0b6ee67..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
new file mode 100644
index 0000000..1d38f185
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/layer-due-to-layer-children-deep-expected.png b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/layer-due-to-layer-children-deep-expected.png
new file mode 100644
index 0000000..4fb90c09
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/geometry/layer-due-to-layer-children-deep-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
deleted file mode 100644
index d90afb61..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/transform-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/transform-interop/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png
new file mode 100644
index 0000000..e7cdb87
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/transform-interop/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/tables/mozilla/bugs/bug137388-2-expected.png b/third_party/blink/web_tests/platform/mac-mac-arm11.0/tables/mozilla/bugs/bug137388-2-expected.png
deleted file mode 100644
index c83c11bd..0000000
--- a/third_party/blink/web_tests/platform/mac-mac-arm11.0/tables/mozilla/bugs/bug137388-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
new file mode 100644
index 0000000..f8d8258
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/tables/mozilla/bugs/bug137388-2-expected.png b/third_party/blink/web_tests/platform/mac/tables/mozilla/bugs/bug137388-2-expected.png
index 77559894..c83c11bd 100644
--- a/third_party/blink/web_tests/platform/mac/tables/mozilla/bugs/bug137388-2-expected.png
+++ b/third_party/blink/web_tests/platform/mac/tables/mozilla/bugs/bug137388-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
new file mode 100644
index 0000000..aa2913ba
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
new file mode 100644
index 0000000..bc1f13bb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
new file mode 100644
index 0000000..c84302f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/3d-corners-expected.png b/third_party/blink/web_tests/platform/win/compositing/3d-corners-expected.png
index 401c2a35..05289d9 100644
--- a/third_party/blink/web_tests/platform/win/compositing/3d-corners-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/3d-corners-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/3d-cube-expected.png b/third_party/blink/web_tests/platform/win/compositing/3d-cube-expected.png
index a8c05a45..ed547bc 100644
--- a/third_party/blink/web_tests/platform/win/compositing/3d-cube-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/3d-cube-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
index 2953aee..0a38bac 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/layer-due-to-layer-children-deep-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/layer-due-to-layer-children-deep-expected.png
index f65d307..20a035f 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/layer-due-to-layer-children-deep-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/layer-due-to-layer-children-deep-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/vertical-scroll-composited-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/vertical-scroll-composited-expected.png
index 8437dc2..4245cef0 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/vertical-scroll-composited-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/vertical-scroll-composited-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/video-fixed-scrolling-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/video-fixed-scrolling-expected.png
index 3c65c87b..0e24860c 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/video-fixed-scrolling-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/video-fixed-scrolling-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/video-opacity-overlay-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/video-opacity-overlay-expected.png
index 15792f6..13fbc00 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/video-opacity-overlay-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/video-opacity-overlay-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-expected.png b/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-expected.png
index cdc5d7e..56516d65 100644
--- a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png
index 62b02fa8..f694a435 100644
--- a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/masks/mask-with-added-filters-expected.png b/third_party/blink/web_tests/platform/win/compositing/masks/mask-with-added-filters-expected.png
index 58dbaf5..e2bc28d 100644
--- a/third_party/blink/web_tests/platform/win/compositing/masks/mask-with-added-filters-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/masks/mask-with-added-filters-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
index 61289a3f..dc5e8a6 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
index 6c1a9ae..d90afb61 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-effect-interleave-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
index 9ad05a3..cf5e66d9 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
index 07d2a97..6aa19b4 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/perspective-interest-rect-expected.png b/third_party/blink/web_tests/platform/win/compositing/perspective-interest-rect-expected.png
index bb49bc7..7a2c3316 100644
--- a/third_party/blink/web_tests/platform/win/compositing/perspective-interest-rect-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/perspective-interest-rect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/reflections/nested-reflection-animated-expected.png b/third_party/blink/web_tests/platform/win/compositing/reflections/nested-reflection-animated-expected.png
index 8d7857e..4f58820 100644
--- a/third_party/blink/web_tests/platform/win/compositing/reflections/nested-reflection-animated-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/reflections/nested-reflection-animated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/video/video-controls-squashing-expected.png b/third_party/blink/web_tests/platform/win/compositing/video/video-controls-squashing-expected.png
index c304295..3ff853f 100644
--- a/third_party/blink/web_tests/platform/win/compositing/video/video-controls-squashing-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/video/video-controls-squashing-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/visibility/visibility-simple-video-layer-expected.png b/third_party/blink/web_tests/platform/win/compositing/visibility/visibility-simple-video-layer-expected.png
index 03cc1cc..843dbae 100644
--- a/third_party/blink/web_tests/platform/win/compositing/visibility/visibility-simple-video-layer-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/visibility/visibility-simple-video-layer-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/webgl/webgl-reflection-expected.png b/third_party/blink/web_tests/platform/win/compositing/webgl/webgl-reflection-expected.png
index d265f0e60..98c197f 100644
--- a/third_party/blink/web_tests/platform/win/compositing/webgl/webgl-reflection-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/webgl/webgl-reflection-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index d30ee8d..0bc0a58a 100644
--- a/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-brightness-clamping-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-brightness-clamping-hw-expected.png
index 40cbdbf9..b37e829 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-brightness-clamping-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-brightness-clamping-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-hidpi-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hidpi-hw-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-hidpi-hw-expected.png
rename to third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hidpi-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hw-expected.png
index 79fd7cc..a04fc69a 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-ordering-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-ordering-hw-expected.png
new file mode 100644
index 0000000..12db1a0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-ordering-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-hw-expected.png
index 3763287..5f03de7 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-zoom-hw-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-zoom-hw-expected.png
index ba67a1b..218dd33f 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-zoom-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-zoom-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-crash-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-crash-expected.png
new file mode 100644
index 0000000..f2436eeb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-expected.png
new file mode 100644
index 0000000..f2436eeb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/css3/filters/filter-repaint-composited-fallback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-canvas-expected.png b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-canvas-expected.png
index 917e17e..4c22d0f2 100644
--- a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-canvas-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-video-expected.png b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-video-expected.png
index f13d8a8..c7461eb 100644
--- a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-video-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-mask-video-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-with-composited-child-expected.png b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-with-composited-child-expected.png
index b749d72..3f57a1d 100644
--- a/third_party/blink/web_tests/platform/win/fast/borders/border-radius-with-composited-child-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/borders/border-radius-with-composited-child-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-menu-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-menu-expected.png
index dadb289e..2272219 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-menu-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-menu-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-play-button-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
index 20a35fb..d73aff29 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-rotate-expected.png b/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-rotate-expected.png
index eb0ad578..a5c6fc95 100644
--- a/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-rotate-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-rotate-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-skew-expected.png b/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-skew-expected.png
index c6fe0fd..a64adc9 100644
--- a/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-skew-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/frames/frame-set-scaling-skew-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/webgl/pixelated-expected.png b/third_party/blink/web_tests/platform/win/fast/webgl/pixelated-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/fast/webgl/pixelated-expected.png
rename to third_party/blink/web_tests/platform/win/fast/webgl/pixelated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-expected.png b/third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-expected.png
rename to third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-repaint-expected.png b/third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-repaint-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-repaint-expected.png
rename to third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-tabswitching-expected.png b/third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-tabswitching-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/fast/webgl/webgl-composite-modes-tabswitching-expected.png
rename to third_party/blink/web_tests/platform/win/fast/webgl/webgl-composite-modes-tabswitching-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/images/yuv-decode-eligible/color-profile-layer-filter-expected.png b/third_party/blink/web_tests/platform/win/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
index 66eeab7..1225ff3 100644
--- a/third_party/blink/web_tests/platform/win/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/color-profile-video-expected.png b/third_party/blink/web_tests/platform/win/media/color-profile-video-expected.png
index 4901dc8..110e5c5 100644
--- a/third_party/blink/web_tests/platform/win/media/color-profile-video-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/color-profile-video-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls-after-reload-expected.png b/third_party/blink/web_tests/platform/win/media/controls-after-reload-expected.png
index 2953838..7a92eb6 100644
--- a/third_party/blink/web_tests/platform/win/media/controls-after-reload-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls-strict-expected.png b/third_party/blink/web_tests/platform/win/media/controls-strict-expected.png
index 03fb4ab..6c6d3a2 100644
--- a/third_party/blink/web_tests/platform/win/media/controls-strict-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls-styling-expected.png b/third_party/blink/web_tests/platform/win/media/controls-styling-expected.png
index 5ebc1e76..04b5440f 100644
--- a/third_party/blink/web_tests/platform/win/media/controls-styling-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls-styling-strict-expected.png b/third_party/blink/web_tests/platform/win/media/controls-styling-strict-expected.png
index 82f69f54..e51ca0e 100644
--- a/third_party/blink/web_tests/platform/win/media/controls-styling-strict-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls-without-preload-expected.png b/third_party/blink/web_tests/platform/win/media/controls-without-preload-expected.png
index 8ccd3e2..2c9f511c 100644
--- a/third_party/blink/web_tests/platform/win/media/controls-without-preload-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/blink/web_tests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
index f687768..e161776 100644
--- a/third_party/blink/web_tests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/video-controls-rendering-expected.png b/third_party/blink/web_tests/platform/win/media/video-controls-rendering-expected.png
index 513458d..988e3249 100644
--- a/third_party/blink/web_tests/platform/win/media/video-controls-rendering-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/video-display-toggle-expected.png b/third_party/blink/web_tests/platform/win/media/video-display-toggle-expected.png
index 9c2605b..ae8204a 100644
--- a/third_party/blink/web_tests/platform/win/media/video-display-toggle-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/video-no-audio-expected.png b/third_party/blink/web_tests/platform/win/media/video-no-audio-expected.png
index 86e52323..5eb00d2 100644
--- a/third_party/blink/web_tests/platform/win/media/video-no-audio-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/video-replaces-poster-expected.png b/third_party/blink/web_tests/platform/win/media/video-replaces-poster-expected.png
index 17f0b6a1..e28b6a3 100644
--- a/third_party/blink/web_tests/platform/win/media/video-replaces-poster-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/video-replaces-poster-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/video-zoom-controls-expected.png b/third_party/blink/web_tests/platform/win/media/video-zoom-controls-expected.png
index be6e10b..34a0905 100644
--- a/third_party/blink/web_tests/platform/win/media/video-zoom-controls-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/tables/mozilla/bugs/bug137388-2-expected.png b/third_party/blink/web_tests/platform/win/tables/mozilla/bugs/bug137388-2-expected.png
index f993e5f..b39895f 100644
--- a/third_party/blink/web_tests/platform/win/tables/mozilla/bugs/bug137388-2-expected.png
+++ b/third_party/blink/web_tests/platform/win/tables/mozilla/bugs/bug137388-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/general/perspective-units-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/general/perspective-units-expected.png
index 32031fd..acad6a4 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/general/perspective-units-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/general/perspective-units-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-2-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-2-expected.png
index 306cb00..fe198c9f 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-2-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png
index 1f16dda..950adfb 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-deep-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-expected.png
index 4413d43..cd0c96a 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-origins-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-origins-expected.png
index 135e53f..74ed2b8 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-origins-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-origins-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-preserve-3d-expected.png b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-preserve-3d-expected.png
index 383e68e..4061944 100644
--- a/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-preserve-3d-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/3d/point-mapping/3d-point-mapping-preserve-3d-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-menu-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-menu-expected.png
index 1ba009c5..6884ba7 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-menu-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-menu-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-play-button-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
index 0f291f2..d4cf30a 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/media/video-overlay-play-button-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/media/video-controls-rendering-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/media/video-controls-rendering-expected.png
index 694cf4b0..daa6ec0e 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/media/video-controls-rendering-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/2-comp-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/2-comp-expected.png
index cc54e35..b9aada3 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/2-comp-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/2-comp-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-jpeg-with-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-jpeg-with-color-profile-expected.png
index 889a3b3ab..3161474 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-jpeg-with-color-profile-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-expected.png
index e78b941..3cf3789b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-rotate-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-rotate-expected.png
index 0e05755..4fa3a4e 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-rotate-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-animate-rotate-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-cover-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-cover-expected.png
index ad98a77..b59a532 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-cover-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-repeat-expected.png
index a7fdfef..d9f2713 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-repeat-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-space-expected.png
index ebcd335..58b34902 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-space-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-border-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-border-image-expected.png
index df9a5bd..89b9df7 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-border-image-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-group-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-group-expected.png
index 9a15d7a..1dab753 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-group-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-expected.png
index e885a10..6f5b90d7 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-pattern-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-pattern-expected.png
index bf0cdf9..14e6a2b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-pattern-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-pattern-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
index ac765d35..1a4b164 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-filter-all-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-filter-all-expected.png
index c191e5c2..c2a405cf 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-filter-all-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-filter-all-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-object-fit-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-object-fit-expected.png
index e8f62fa..5688f87 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-object-fit-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-object-fit-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-shape-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-shape-expected.png
index 22b7fe3..a79ccf8 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-shape-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-shape-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-svg-resource-url-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-svg-resource-url-expected.png
index 80fc4fc..657d4ce422 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-svg-resource-url-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-image-svg-resource-url-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-mask-image-svg-expected.png
index cc7f30b0..1b747ed 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-mask-image-svg-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-mask-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-object-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-object-expected.png
index e1be2312..0d110cf 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-object-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-svg-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-svg-expected.png
index b1782b3..3035e41 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-svg-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-with-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-with-color-profile-expected.png
index 0cd875c..013a176 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-with-color-profile-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
index e680ad7..bdb3350b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-image-expected.png
index cb9d2d3..264444b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-image-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/motion-jpeg-single-frame-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/motion-jpeg-single-frame-expected.png
index eb4df96..f9a4bc5 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/motion-jpeg-single-frame-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/motion-jpeg-single-frame-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/png-with-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/png-with-color-profile-expected.png
index 83b897a..b547326 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/png-with-color-profile-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/png-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossless-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossless-expected.png
index 9063e780..b4ae1928b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossless-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossless-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossy-alpha-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossy-alpha-expected.png
index 398b2fa..5aaed9a3 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossy-alpha-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/webp-color-profile-lossy-alpha-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-border-radius-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-border-radius-expected.png
index 339700f..afcbac3 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-border-radius-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-border-radius-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-filter-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-filter-expected.png
index b400e43..4c8425c9 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-expected.png
index f0f8858d..e37fe5d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
index bf53ed4..ec586f83 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-expected.png
index 7a0256f..fb8aa4c 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-filter-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
index 1cd13fb6..ab7a6f4 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
index eaf5baf3..79ee70a 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png
index db8f1a0..1edf32d8 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-color-profile-lossy-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png
index 193a648..3efbc1d1 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/yuv-decode-eligible/webp-no-color-profile-lossy-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-filter-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-filter-expected.png
index bf16f36..d454902 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
new file mode 100644
index 0000000..827714e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
new file mode 100644
index 0000000..a45ce53
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization-disable-yuv/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-jpeg-with-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-jpeg-with-color-profile-expected.png
new file mode 100644
index 0000000..8bd187f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
new file mode 100644
index 0000000..4c15fc7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
index 22971fe..9e283aa 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
index 22971fe..9e283aa 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..28884058
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..707b2e2
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-border-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-border-image-expected.png
new file mode 100644
index 0000000..f5cbeb24
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..5aa485c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
index fb41587..a65992e 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
new file mode 100644
index 0000000..5f0767e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-object-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-object-expected.png
new file mode 100644
index 0000000..c1b95aa
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
index b49d31e..102a636d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/cross-fade-overflow-position-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-overflow-position-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/cross-fade-overflow-position-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-overflow-position-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
index eacd08b..807abd7 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png
new file mode 100644
index 0000000..85e32a7c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/rgb-png-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
new file mode 100644
index 0000000..2500a7a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png
index 2c4ea48c..8c343f83 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png
index 42f5cfa4..8c94dd1 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
new file mode 100644
index 0000000..a58c85a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
new file mode 100644
index 0000000..37fcecf
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/yuv-decode-eligible/jpeg-missing-eoi-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-composite-video-shadow-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-composite-video-shadow-expected.png
index 470cb20..3fcb4484 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-composite-video-shadow-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-composite-video-shadow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
index 96a4313..48a46384f 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-webp-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-webp-expected.png
index 617cb478..84e1d7f 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-webp-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-toDataURL-webp-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/quadraticCurveTo-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/quadraticCurveTo-expected.png
index e7518d0..c1f645d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/quadraticCurveTo-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/quadraticCurveTo-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-composite-video-shadow-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-composite-video-shadow-expected.png
index 470cb20..3fcb4484 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-composite-video-shadow-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-composite-video-shadow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-text-alignment-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-text-alignment-expected.png
index f192380..4d73d8d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-text-alignment-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-text-alignment-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
index b96913a..8c7e7f9 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-webp-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-webp-expected.png
index 8f89a1f..b60f2531 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-webp-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/canvas-toDataURL-webp-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/fillrect_gradient-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/fillrect_gradient-expected.png
index e444bd3..72b9ea3 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/fillrect_gradient-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/fillrect_gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png
index fa80101..95b8657 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png
index e7518d0..c1f645d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
index 487a936..0b6ee67 100644
--- a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
index 9ad05a3..cf5e66d9 100644
--- a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
index 07d2a97..6aa19b4 100644
--- a/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/prefer_compositing_to_lcd_text/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-basic-blur-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-basic-blur-expected.png
index e344dc0..33e1bfb 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-basic-blur-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-basic-blur-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-boundary-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-boundary-expected.png
index 24ebc968..b45fc6c 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-boundary-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/backdrop-filter-boundary-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png
index b5935ee0..2cad578 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-blur-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-blur-hw-expected.png
index 58a9c42..f5396e6b 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-blur-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-blur-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png
index 255bc8d..62542972 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
index a61e756..07a086a4 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
index d4ff3f06..c744fce 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-colorspace-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-hidpi-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hidpi-hw-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-hidpi-hw-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hidpi-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hw-expected.png
index d8296c5..e7047599 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-ordering-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-ordering-hw-expected.png
new file mode 100644
index 0000000..4befff7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-ordering-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-hw-expected.png
index 77e84a7..8c867cd 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
index 3668a5c..d9519dee 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png
new file mode 100644
index 0000000..57e724c7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png
new file mode 100644
index 0000000..40e8bcb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png
new file mode 100644
index 0000000..40e8bcb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/pixelated-expected.png b/third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/pixelated-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/pixelated-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/pixelated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-expected.png b/third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-repaint-expected.png b/third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-repaint-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-repaint-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-tabswitching-expected.png b/third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-tabswitching-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/linux/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-tabswitching-expected.png
rename to third_party/blink/web_tests/platform/win/virtual/shared_array_buffer_on_desktop/fast/webgl/webgl-composite-modes-tabswitching-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index aa2913ba..0e9fc25 100644
--- a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
index bc1f13bb..2ac683c 100644
--- a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
+++ b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-pattern-no-repeat-with-transformations-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
index c84302f..266c825 100644
--- a/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
+++ b/third_party/blink/web_tests/virtual/oopr-canvas2d/fast/canvas/gradient-add-second-start-end-stop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index fb1f183..2210b877 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -8097,7 +8097,9 @@
     method constructor
     setter zoomAndPan
 interface Sanitizer
+    static method defaultConfig
     attribute @@toStringTag
+    method config
     method constructor
     method sanitize
     method sanitizeToString
diff --git a/third_party/blink/web_tests/wpt_internal/sanitizer-api/sanitizer-query-config.https.tenative.html b/third_party/blink/web_tests/wpt_internal/sanitizer-api/sanitizer-query-config.https.tenative.html
new file mode 100644
index 0000000..51505c6
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/sanitizer-api/sanitizer-query-config.https.tenative.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+  function assert_deep_equals(obj1, obj2) {
+    assert_equals(typeof obj1, typeof obj2);
+    if (typeof obj1 == "string") {
+      assert_equals(obj1, obj2);
+    } else if (typeof obj1 == "boolean") {
+      assert_true(obj1 == obj2);
+    } else if (typeof obj1 == "object") {
+      assert_array_equals(Object.keys(obj1).sort(), Object.keys(obj2).sort());
+      for (const k in Object.keys(obj1))
+        assert_deep_equals(obj1[k], obj2[k]);
+    }
+  }
+
+  test(t => {
+    // Quick sanity test: Test a few default values.
+    assert_in_array("div", Sanitizer.defaultConfig().allowElements);
+    assert_false(Sanitizer.defaultConfig().allowElements.includes("script"));
+    assert_false(Sanitizer.defaultConfig().allowElements.includes("noscript"));
+
+    assert_true("span" in Sanitizer.defaultConfig().allowAttributes);
+    assert_false("onclick" in Sanitizer.defaultConfig().allowAttributes);
+
+    assert_false("dropElements" in Sanitizer.defaultConfig());
+    assert_false("blockElements" in Sanitizer.defaultConfig());
+    assert_false("dropAttributes" in Sanitizer.defaultConfig());
+    assert_false(Sanitizer.defaultConfig().allowCustomElements);
+  }, "SanitizerAPI defaultConfig()");
+
+  test(t => {
+    assert_deep_equals(Sanitizer.defaultConfig(),
+                       new Sanitizer().config());
+  }, "SanitizerAPI config() on default created Sanitizer");
+
+  test(t => {
+    const configs = [{
+      allowElements: ["div", "sPAn", "helloworld"],
+      dropElements: ["xxx"],
+      allowAttributes: { "class": ["*"], "color": ["span", "div"],
+                         "onclick": ["*"] },
+      allowCustomElements: true,
+    },{
+      blockElements: ["table", "tbody", "th", "td"],
+    }, {
+      allowCustomElements: false,
+    }];
+    for (const config of configs)
+      assert_deep_equals(config, new Sanitizer(config).config());
+  }, "SanitizerAPI config() reflects creation config.");
+</script>
+</body>
+</html>
diff --git a/tools/android/modularization/convenience/lookup_dep.py b/tools/android/modularization/convenience/lookup_dep.py
index 69081bd7..ecfa6f0 100755
--- a/tools/android/modularization/convenience/lookup_dep.py
+++ b/tools/android/modularization/convenience/lookup_dep.py
@@ -20,10 +20,12 @@
 import dataclasses
 import json
 import logging
+import os
 import pathlib
 import subprocess
 import sys
-from typing import Dict, List
+import zipfile
+from typing import Dict, List, Set
 
 _SRC_DIR = pathlib.Path(__file__).parents[4].resolve()
 
@@ -154,27 +156,78 @@
       assert len(target_line_parts) == 2, target_line_parts
       target, build_config_path = target_line_parts
 
-      # Read the location of the java_sources_file from the build_config
-      with open(build_config_path) as build_config_contents:
-        build_config: Dict = json.load(build_config_contents)
-      deps_info = build_config['deps_info']
-      sources_path = deps_info.get('java_sources_file')
-      if not sources_path:
-        # TODO(crbug.com/1108362): Handle targets that have no
-        # deps_info.sources_path but contain srcjars.
-        continue
+      target = self._compute_toplevel_target(target)
+      full_class_names = self._compute_full_class_names_for_build_config(
+          build_config_path)
+      for full_class_name in full_class_names:
+        class_index[full_class_name].append(target)
 
+    return class_index
+
+  def _compute_toplevel_target(self, target: str) -> str:
+    """Computes top level target from the passed-in sub-target."""
+    if target.endswith('_java'):
+      return target
+
+    # Handle android_aar_prebuilt() sub targets.
+    index = target.find('_java__subjar')
+    if index >= 0:
+      return target[0:index + 5]
+    index = target.find('_java__classes')
+    if index >= 0:
+      return target[0:index + 5]
+
+    return target
+
+  def _compute_full_class_names_for_build_config(self, build_config_path: str
+                                                 ) -> Set[str]:
+    """Returns set of fully qualified class names for build config."""
+    with open(build_config_path) as build_config_contents:
+      build_config: Dict = json.load(build_config_contents)
+    deps_info = build_config['deps_info']
+
+    # Read the location of the java_sources_file from the build_config
+    sources_path = deps_info.get('java_sources_file')
+    if sources_path:
       # Read the java_sources_file, indexing the classes found
       with open(self._build_output_dir / sources_path) as sources_contents:
-        sources_lines = sources_contents
-        for source_line in sources_lines:
+        out = set()
+        for source_line in sources_contents:
           source_path = pathlib.Path(source_line.strip())
           java_class = self._parse_full_java_class(source_path)
           if java_class:
-            class_index[java_class].append(target)
-          continue
+            out.add(java_class)
+        return out
 
-    return class_index
+    # |unprocessed_jar_path| is set for prebuilt targets. (ex:
+    # android_aar_prebuilt())
+    # |unprocessed_jar_path| might be set but not exist if not all targets have
+    # been built.
+    unprocessed_jar_path = self._build_output_dir / deps_info.get(
+        'unprocessed_jar_path')
+    if unprocessed_jar_path and os.path.exists(unprocessed_jar_path):
+      return self._extract_full_class_names_from_jar(unprocessed_jar_path)
+
+    return set()
+
+  def _extract_full_class_names_from_jar(self,
+                                         jar_path: pathlib.Path) -> Set[str]:
+    """Returns set of fully qualified class names in passed-in jar."""
+    out = set()
+    with zipfile.ZipFile(jar_path) as z:
+      for zip_entry_name in z.namelist():
+        if not zip_entry_name.endswith('.class'):
+          continue
+        # Remove .class suffix
+        full_java_class = zip_entry_name[:-6]
+
+        full_java_class = full_java_class.replace('/', '.')
+        dollar_index = full_java_class.find('$')
+        if dollar_index >= 0:
+          full_java_class[0:dollar_index]
+
+        out.add(full_java_class)
+    return out
 
   def _parse_full_java_class(self, source_path: pathlib.Path) -> str:
     """Guess the fully qualified class name from the path to the source file."""
diff --git a/tools/json_schema_compiler/util.cc b/tools/json_schema_compiler/util.cc
index 0d144ca6..41cb9ea 100644
--- a/tools/json_schema_compiler/util.cc
+++ b/tools/json_schema_compiler/util.cc
@@ -94,14 +94,14 @@
 }
 
 bool PopulateItem(const base::Value& from, std::unique_ptr<base::Value>* out) {
-  *out = from.CreateDeepCopy();
+  *out = base::Value::ToUniquePtrValue(from.Clone());
   return true;
 }
 
 bool PopulateItem(const base::Value& from,
                   std::unique_ptr<base::Value>* out,
                   std::u16string* error) {
-  *out = from.CreateDeepCopy();
+  *out = base::Value::ToUniquePtrValue(from.Clone());
   return true;
 }
 
@@ -146,7 +146,7 @@
 
 void AddItemToList(const std::unique_ptr<base::Value>& from,
                    base::ListValue* out) {
-  out->Append(from->CreateDeepCopy());
+  out->Append(from->Clone());
 }
 
 void AddItemToList(const std::unique_ptr<base::DictionaryValue>& from,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 404e888..9e9b6bc 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -5079,6 +5079,8 @@
   <int value="7" label="Feature disabled"/>
   <int value="8" label="No initial url"/>
   <int value="9" label="Mandatory parameter missing"/>
+  <int value="10" label="Navigated away"/>
+  <int value="11" label="Navigation error"/>
 </enum>
 
 <enum name="AutofillAssistantOnBoarding">
@@ -32381,6 +32383,8 @@
   <int value="3894" label="HandwritingRecognitionStartDrawing"/>
   <int value="3895" label="HandwritingRecognitionGetPrediction"/>
   <int value="3896" label="WebBluetoothManufacturerDataFilter"/>
+  <int value="3897" label="SanitizerAPIGetConfig"/>
+  <int value="3898" label="SanitizerAPIGetDefaultConfig"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -34171,6 +34175,9 @@
   <summary>Stages of the first run experience.</summary>
   <int value="0" label="Start First Run"/>
   <int value="1" label="Complete First Run"/>
+  <int value="2" label="Show sync screen"/>
+  <int value="3" label="Close sync screen with sync"/>
+  <int value="4" label="Close sync screen without sync"/>
 </enum>
 
 <enum name="FirstUserActionType">
@@ -47491,6 +47498,7 @@
   <int value="647662142" label="use-angle"/>
   <int value="649111851" label="MidiManagerCros:enabled"/>
   <int value="649508040" label="AutofillEnableCompanyName:enabled"/>
+  <int value="650013099" label="PrivacySandboxSettings2:disabled"/>
   <int value="651421878" label="VideoRotateToFullscreen:enabled"/>
   <int value="651471603" label="MediaFoundationD3D11VideoCapture:enabled"/>
   <int value="651562604" label="RawClipboard:enabled"/>
@@ -47663,6 +47671,7 @@
   <int value="783270752" label="AndroidHistoryManager:enabled"/>
   <int value="787080596" label="DynamicTcmallocTuning:enabled"/>
   <int value="787385958" label="RegionalLocalesAsDisplayUI:enabled"/>
+  <int value="788130042" label="PrivacySandboxSettings2:enabled"/>
   <int value="792307132"
       label="KeyboardBasedDisplayArrangementInSettings:enabled"/>
   <int value="792884862" label="EnableSharedImageForWebview:enabled"/>
@@ -62856,6 +62865,7 @@
   <int value="7" label="kCharging"/>
   <int value="8" label="kNopAnimation"/>
   <int value="9" label="kVideoPlayback"/>
+  <int value="10" label="kLoadingAnimation"/>
 </enum>
 
 <enum name="PowerSupplyType">
@@ -76684,6 +76694,13 @@
   <int value="14" label="Announcement notification open button."/>
   <int value="15" label="TWA notification 'Got it' button."/>
   <int value="16" label="Auto fetch offline page cancel button."/>
+  <int value="17" label="Media notification play button."/>
+  <int value="18" label="Media notification pause button."/>
+  <int value="19" label="Media notification stop button."/>
+  <int value="20" label="Media notification previous track button."/>
+  <int value="21" label="Media notification next track button."/>
+  <int value="22" label="Media notification seek forward button."/>
+  <int value="23" label="Media notification seek backward button."/>
 </enum>
 
 <enum name="SystemNotificationType">
diff --git a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
index 86bfe951..09c0efe 100644
--- a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
@@ -12,6 +12,7 @@
 harrisonsean@chromium.org
 iclelland@chromium.org
 javierrobles@chromium.org
+jsaul@google.com
 lizeb@chromium.org
 lyf@chromium.org
 mcrouse@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/autofill/OWNERS b/tools/metrics/histograms/histograms_xml/autofill/OWNERS
index 58e1a17..5f6f766 100644
--- a/tools/metrics/histograms/histograms_xml/autofill/OWNERS
+++ b/tools/metrics/histograms/histograms_xml/autofill/OWNERS
@@ -2,4 +2,5 @@
 
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
-vsemeniuk@google.com
+jsaul@google.com # Autofill (Payments org)
+vsemeniuk@google.com # Passwords
diff --git a/tools/metrics/histograms/histograms_xml/disk/histograms.xml b/tools/metrics/histograms/histograms_xml/disk/histograms.xml
index 5cc0b78c..25ea916 100644
--- a/tools/metrics/histograms/histograms_xml/disk/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/disk/histograms.xml
@@ -1714,41 +1714,65 @@
 </histogram>
 
 <histogram name="DiskCache.BlockLoad_0" units="%" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The percentage of use for blocks of type 0 (links).</summary>
 </histogram>
 
 <histogram name="DiskCache.BlockLoad_1" units="%" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The percentage of use for blocks of type 1 (256 bytes).</summary>
 </histogram>
 
 <histogram name="DiskCache.BlockLoad_2" units="%" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The percentage of use for blocks of type 2 (1024 bytes).</summary>
 </histogram>
 
 <histogram name="DiskCache.BlockLoad_3" units="%" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The percentage of use for blocks of type 3 (4096 bytes).</summary>
 </histogram>
 
 <histogram name="DiskCache.Blocks_0" units="blocks" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The number of used blocks of type 0 (links).</summary>
 </histogram>
 
 <histogram name="DiskCache.Blocks_1" units="blocks" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The number of used blocks of type 1 (256 bytes).</summary>
 </histogram>
 
 <histogram name="DiskCache.Blocks_2" units="blocks" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The number of used blocks of type 2 (1024 bytes).</summary>
 </histogram>
 
 <histogram name="DiskCache.Blocks_3" units="blocks" expires_after="M79">
+  <obsolete>
+    Removed 05/2021. Not used.
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>The number of used blocks of type 3 (4096 bytes).</summary>
 </histogram>
diff --git a/tools/metrics/histograms/histograms_xml/ios/histograms.xml b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
index 8e24197..daf6356b 100644
--- a/tools/metrics/histograms/histograms_xml/ios/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ios/histograms.xml
@@ -301,6 +301,36 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.DefaultBrowserFullscreenTailoredPromoAllTabs"
+    enum="IOSDefaultBrowserFullscreenPromoAction" expires_after="2022-02-01">
+  <owner>javierrobles@chromium.org</owner>
+  <owner>rkgibson@chromium.org</owner>
+  <summary>
+    The action taken by the user in response to the &quot;All tabs&quot; default
+    browser tailored promo.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultBrowserFullscreenTailoredPromoMadeForIOS"
+    enum="IOSDefaultBrowserFullscreenPromoAction" expires_after="2022-02-01">
+  <owner>javierrobles@chromium.org</owner>
+  <owner>rkgibson@chromium.org</owner>
+  <summary>
+    The action taken by the user in response to the &quot;Made for iOS&quot;
+    default browser tailored promo.
+  </summary>
+</histogram>
+
+<histogram name="IOS.DefaultBrowserFullscreenTailoredPromoStaySafe"
+    enum="IOSDefaultBrowserFullscreenPromoAction" expires_after="2022-02-01">
+  <owner>javierrobles@chromium.org</owner>
+  <owner>rkgibson@chromium.org</owner>
+  <summary>
+    The action taken by the user in response to the &quot;Stay Safe&quot;
+    default browser tailored promo.
+  </summary>
+</histogram>
+
 <histogram name="IOS.DefaultBrowserPromo.TailoredFullscreen.{Action}"
     enum="IOSDefaultBrowserTailoredPromoType" expires_after="2022-04-20">
   <owner>javierrobles@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/power/histograms.xml b/tools/metrics/histograms/histograms_xml/power/histograms.xml
index 1a06aef..2dcf6e20 100644
--- a/tools/metrics/histograms/histograms_xml/power/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/power/histograms.xml
@@ -983,6 +983,22 @@
   </summary>
 </histogram>
 
+<histogram name="Power.PowerScheduler.ProcessPowerModeChange.{ProcessType}"
+    enum="PowerMode" expires_after="2021-10-04">
+  <owner>eseckler@chromium.org</owner>
+  <owner>skyostil@chromium.org</owner>
+  <summary>
+    Records the new value of the {ProcessType} process power mode every time a
+    change is detected (happens on the order of once per second).
+  </summary>
+  <token key="ProcessType">
+    <variant name="Browser" summary="browser"/>
+    <variant name="GPU" summary="GPU"/>
+    <variant name="Other" summary="other type of"/>
+    <variant name="Renderer" summary="renderer"/>
+  </token>
+</histogram>
+
 <histogram name="Power.PowerScheduler.ThrottlingDuration" units="ms"
     expires_after="2021-10-29">
   <owner>eseckler@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/search/histograms.xml b/tools/metrics/histograms/histograms_xml/search/histograms.xml
index 4ab6412..4bf333c 100644
--- a/tools/metrics/histograms/histograms_xml/search/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/search/histograms.xml
@@ -1240,7 +1240,7 @@
 </histogram>
 
 <histogram base="true" name="Search.QueryTiles.Bitmap.Available"
-    enum="BooleanAvailable" expires_after="M91">
+    enum="BooleanAvailable" expires_after="M94">
 <!-- Name completed by histogram_suffixes name="TileUiSurface" -->
 
   <owner>shaktisahu@chromium.org</owner>
@@ -1251,7 +1251,7 @@
 </histogram>
 
 <histogram base="true" name="Search.QueryTiles.Bitmap.FetchDuration" units="ms"
-    expires_after="M91">
+    expires_after="M94">
 <!-- Name completed by histogram_suffixes name="TileUiSurface" -->
 
   <owner>shaktisahu@chromium.org</owner>
@@ -1264,8 +1264,8 @@
 </histogram>
 
 <histogram name="Search.QueryTiles.Fetcher.FirstFlowDuration" units="hours"
-    expires_after="M91">
-  <owner>hesen@chromium.org</owner>
+    expires_after="M94">
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
     Records duration from time when first task scheduled to time the fetch task
@@ -1274,29 +1274,29 @@
 </histogram>
 
 <histogram name="Search.QueryTiles.Fetcher.Start" units="hours"
-    expires_after="M91">
-  <owner>hesen@chromium.org</owner>
+    expires_after="M94">
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>Records the hour (0-23) when the TileFetcher task starts.</summary>
 </histogram>
 
 <histogram name="Search.QueryTiles.FetcherHttpResponseCode"
     enum="HttpResponseCode" expires_after="2021-10-25">
-  <owner>hesen@chromium.org</owner>
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>Records the HTTP response code get from TileFetcher.</summary>
 </histogram>
 
 <histogram name="Search.QueryTiles.FetcherNetErrorCode" enum="NetErrorCodes"
     expires_after="2021-08-09">
-  <owner>hesen@chromium.org</owner>
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>Records the net error code get from TileFetcher.</summary>
 </histogram>
 
 <histogram name="Search.QueryTiles.Group.PruneReason"
-    enum="QueryTilesGroupPruneReason" expires_after="M91">
-  <owner>hesen@chromium.org</owner>
+    enum="QueryTilesGroupPruneReason" expires_after="M94">
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
     Records the reasons caused the tile group pruned in TileManager.
@@ -1305,7 +1305,7 @@
 
 <histogram name="Search.QueryTiles.GroupStatus" enum="QueryTilesGroupStatus"
     expires_after="2021-08-22">
-  <owner>hesen@chromium.org</owner>
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
     Records the TileManager status after initialized and loaded completed.
@@ -1323,7 +1323,7 @@
 </histogram>
 
 <histogram base="true" name="Search.QueryTiles.NoBitmap.FetchDuration"
-    units="ms" expires_after="M91">
+    units="ms" expires_after="M94">
 <!-- Name completed by histogram_suffixes name="TileUiSurface" -->
 
   <owner>shaktisahu@chromium.org</owner>
@@ -1346,8 +1346,8 @@
 </histogram>
 
 <histogram name="Search.QueryTiles.RequestStatus"
-    enum="QueryTilesRequestStatus" expires_after="M91">
-  <owner>hesen@chromium.org</owner>
+    enum="QueryTilesRequestStatus" expires_after="M94">
+  <owner>qinmin@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
     Records the TileFetcher status after request send to server and response
@@ -1377,7 +1377,7 @@
 </histogram>
 
 <histogram base="true" name="Search.QueryTiles.TileCount" units="tiles"
-    expires_after="M91">
+    expires_after="M94">
 <!-- Name completed by histogram_suffixes name="TileUiSurface" -->
 
   <owner>shaktisahu@chromium.org</owner>
@@ -1389,7 +1389,7 @@
 </histogram>
 
 <histogram name="Search.QueryTiles.TilesFitPerRow" units="tiles"
-    expires_after="M91">
+    expires_after="M94">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
@@ -1398,7 +1398,7 @@
   </summary>
 </histogram>
 
-<histogram name="Search.QueryTiles.TileWidth" units="dp" expires_after="M91">
+<histogram name="Search.QueryTiles.TileWidth" units="dp" expires_after="M94">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/translate/histograms.xml b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
index c491d4f..0ee0a567 100644
--- a/tools/metrics/histograms/histograms_xml/translate/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
@@ -723,6 +723,11 @@
 
 <histogram name="Translate.PageLoad.TriggerDecision.AllTriggerDecisions"
     enum="TranslateTriggerDecision" expires_after="M92">
+  <obsolete>
+    Removed M92. This histogram was used to estimate the impact of masking on
+    Translate.PageLoad.TriggerDecision. We found that masking was having minimal
+    impact, so this histogram is no longer needed.
+  </obsolete>
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -740,6 +745,11 @@
 
 <histogram name="Translate.PageLoad.TriggerDecision.TotalCount"
     units="trigger decisions" expires_after="M92">
+  <obsolete>
+    Removed M92. This histogram was used to estimate the impact of masking on
+    Translate.PageLoad.TriggerDecision. We found that masking w as having
+    minimal impact, so this histogram is no longer needed.
+  </obsolete>
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index c0116170..3563007 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -68,6 +68,9 @@
 
 # Benchmark: blink_perf.shadow_dom
 crbug.com/702319 [ android-nexus-5x ] blink_perf.shadow_dom/* [ Skip ]
+crbug.com/1205874 [ linux ] blink_perf.shadow_dom/imperative-api-assigned-slot.html [ Skip ]
+crbug.com/1205874 [ win ] blink_perf.shadow_dom/imperative-api-assigned-slot.html [ Skip ]
+crbug.com/1205874 [ mac ] blink_perf.shadow_dom/imperative-api-assigned-slot.html [ Skip ]
 
 # Benchmark: desktop_ui
 crbug.com/1205850 [ win ] desktop_ui/tab_search:measure_memory* [ Skip ]
diff --git a/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc b/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc
index 9b26c38..9059eca 100644
--- a/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc
+++ b/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.cc
@@ -188,6 +188,7 @@
     {DomCode::BACKQUOTE, 0, 0, kAny, kAny, VKEY_OEM_7},
     {DomCode::BACKSLASH, 0, 0, kAny, kAny, VKEY_OEM_5},
     {DomCode::SLASH, 0, 0, kAny, kAny, VKEY_OEM_2},
+    {DomCode::CONTROL_RIGHT, 0, 0, kAny, kAny, VKEY_RCONTROL},
     {DomCode::DIGIT3, 1, 0, 0x0033, kAny, VKEY_3},       // 3
     {DomCode::DIGIT3, 1, 0, 0x003F, kAny, VKEY_OEM_2},   // ?
     {DomCode::DIGIT0, 1, 0, 0x0030, kAny, VKEY_0},       // 0
diff --git a/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc b/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc
index 803624e..4090eff3 100644
--- a/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc
+++ b/ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine_unittest.cc
@@ -251,8 +251,8 @@
       // plus sign, *, *
       /* 41 */ {{0x002B, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_PLUS},
       // plus sign, *, *
-      /* 42 */ {{0x002B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT},
-                VKEY_OEM_PLUS},
+      /* 42 */
+      {{0x002B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_PLUS},
       // plus sign, *, *
       /* 43 */ {{0x002B, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
       // plus sign, *, *
@@ -333,433 +333,437 @@
       /* 81 */ {{0x002F, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_4},
       // solidus, *, *
       /* 82 */ {{0x002F, 0x2460, 0x2461, DomCode::SLASH}, VKEY_OEM_2},
+      // solidus, *, *
+      /* 83 */
+      {{0x002F, 0x2460, 0x2461, DomCode::CONTROL_RIGHT}, VKEY_RCONTROL},
       // colon, *, *
-      /* 83 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
+      /* 84 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
       // colon, *, *
-      /* 84 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT5}, VKEY_5},
+      /* 85 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT5}, VKEY_5},
       // colon, *, *
-      /* 85 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
+      /* 86 */ {{0x003A, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
       // colon, *, *
-      /* 86 */ {{0x003A, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_2},
+      /* 87 */ {{0x003A, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_2},
       // semicolon, *, *
-      /* 87 */ {{0x003B, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 88 */ {{0x003B, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // semicolon, *, *
-      /* 88 */ {{0x003B, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
+      /* 89 */ {{0x003B, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
       // semicolon, *, *
-      /* 89 */ {{0x003B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 90 */ {{0x003B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // semicolon, *, *
-      /* 90 */ {{0x003B, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_PERIOD},
+      /* 91 */ {{0x003B, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_PERIOD},
       // semicolon, *, *
-      /* 91 */ {{0x003B, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
+      /* 92 */ {{0x003B, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
       // semicolon, *, *
-      /* 92 */ {{0x003B, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 93 */ {{0x003B, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // semicolon, *, *
-      /* 93 */ {{0x003B, 0x2460, 0x2461, DomCode::US_Q}, VKEY_OEM_1},
+      /* 94 */ {{0x003B, 0x2460, 0x2461, DomCode::US_Q}, VKEY_OEM_1},
       // semicolon, *, *
-      /* 94 */ {{0x003B, 0x2460, 0x2461, DomCode::US_Z}, VKEY_OEM_1},
+      /* 95 */ {{0x003B, 0x2460, 0x2461, DomCode::US_Z}, VKEY_OEM_1},
       // semicolon, *, *
-      /* 95 */ {{0x003B, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 96 */ {{0x003B, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // semicolon, *, *
-      /* 96 */ {{0x003B, 0x2460, 0x2461, DomCode::SLASH}, VKEY_OEM_2},
+      /* 97 */ {{0x003B, 0x2460, 0x2461, DomCode::SLASH}, VKEY_OEM_2},
       // less-than sign, *, *
-      /* 97 */ {{0x003C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 98 */ {{0x003C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // equals sign, percent sign, unmapped
-      /* 98 */ {{0x003D, 0x0025, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
+      /* 99 */ {{0x003D, 0x0025, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
       // equals sign, percent sign, hyphen-minus
-      /* 99 */ {{0x003D, 0x0025, 0x002D, DomCode::MINUS}, VKEY_OEM_MINUS},
+      /* 100 */ {{0x003D, 0x0025, 0x002D, DomCode::MINUS}, VKEY_OEM_MINUS},
       // equals sign, percent sign, *
-      /* 100 */ {{0x003D, 0x0025, 0x2461, DomCode::SLASH}, VKEY_OEM_8},
+      /* 101 */ {{0x003D, 0x0025, 0x2461, DomCode::SLASH}, VKEY_OEM_8},
       // equals sign, plus sign, *
-      /* 101 */ {{0x003D, 0x002B, 0x2461, DomCode::SLASH}, VKEY_OEM_PLUS},
+      /* 102 */ {{0x003D, 0x002B, 0x2461, DomCode::SLASH}, VKEY_OEM_PLUS},
       // equals sign, *, *
-      /* 102 */ {{0x003D, 0x2460, 0x2461, DomCode::BRACKET_RIGHT},
-                 VKEY_OEM_PLUS},
+      /* 103 */
+      {{0x003D, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_PLUS},
       // equals sign, *, *
-      /* 103 */ {{0x003D, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 104 */ {{0x003D, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // equals sign, *, *
-      /* 104 */ {{0x003D, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
+      /* 105 */ {{0x003D, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
       // question mark, *, *
-      /* 105 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
+      /* 106 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
       // question mark, *, *
-      /* 106 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
+      /* 107 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
       // question mark, *, *
-      /* 107 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 108 */ {{0x003F, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // question mark, *, *
-      /* 108 */ {{0x003F, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
+      /* 109 */ {{0x003F, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
       // commercial at, *, *
-      /* 109 */ {{0x0040, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_7},
+      /* 110 */ {{0x0040, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_7},
       // commercial at, *, *
-      /* 110 */ {{0x0040, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 111 */ {{0x0040, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // left square bracket, *, *
-      /* 111 */ {{0x005B, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 112 */ {{0x005B, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // left square bracket, *, *
-      /* 112 */ {{0x005B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 113 */ {{0x005B, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // left square bracket, *, *
-      /* 113 */ {{0x005B, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_OEM_4},
+      /* 114 */ {{0x005B, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_OEM_4},
       // left square bracket, *, *
-      /* 114 */ {{0x005B, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_4},
+      /* 115 */ {{0x005B, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_4},
       // left square bracket, *, *
-      /* 115 */ {{0x005B, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 116 */ {{0x005B, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // reverse solidus, solidus, *
-      /* 116 */ {{0x005C, 0x002F, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_7},
+      /* 117 */ {{0x005C, 0x002F, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_7},
       // reverse solidus, vertical line, digit one
-      /* 117 */ {{0x005C, 0x007C, 0x0031, DomCode::BACKQUOTE}, VKEY_OEM_5},
+      /* 118 */ {{0x005C, 0x007C, 0x0031, DomCode::BACKQUOTE}, VKEY_OEM_5},
       // reverse solidus, vertical line, N cedilla
-      /* 118 */ {{0x005C, 0x007C, 0x0145, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 119 */ {{0x005C, 0x007C, 0x0145, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // reverse solidus, vertical line, *
-      /* 119 */ {{0x005C, 0x007C, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 120 */ {{0x005C, 0x007C, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // reverse solidus, *, *
-      /* 120 */ {{0x005C, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_4},
+      /* 121 */ {{0x005C, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_4},
       // right square bracket, *, *
-      /* 121 */ {{0x005D, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 122 */ {{0x005D, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // right square bracket, *, *
-      /* 122 */ {{0x005D, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 123 */ {{0x005D, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // right square bracket, *, *
-      /* 123 */ {{0x005D, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 124 */ {{0x005D, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // right square bracket, *, *
-      /* 124 */ {{0x005D, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_OEM_6},
+      /* 125 */ {{0x005D, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_OEM_6},
       // right square bracket, *, *
-      /* 125 */ {{0x005D, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_6},
+      /* 126 */ {{0x005D, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_6},
       // low line, *, *
-      /* 126 */ {{0x005F, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 127 */ {{0x005F, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // low line, *, *
-      /* 127 */ {{0x005F, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_MINUS},
+      /* 128 */ {{0x005F, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_MINUS},
       // grave accent, unmapped, *
-      /* 128 */ {{0x0060, 0x0000, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 129 */ {{0x0060, 0x0000, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, unmapped
-      /* 129 */ {{0x0060, 0x007E, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 130 */ {{0x0060, 0x007E, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, digit one
-      /* 130 */ {{0x0060, 0x007E, 0x0031, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 131 */ {{0x0060, 0x007E, 0x0031, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, semicolon
-      /* 131 */ {{0x0060, 0x007E, 0x003B, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 132 */ {{0x0060, 0x007E, 0x003B, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, grave accent
-      /* 132 */ {{0x0060, 0x007E, 0x0060, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 133 */ {{0x0060, 0x007E, 0x0060, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, inverted question mark
-      /* 133 */ {{0x0060, 0x007E, 0x00BF, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 134 */ {{0x0060, 0x007E, 0x00BF, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, tilde, o double acute
-      /* 134 */ {{0x0060, 0x007E, 0x0151, DomCode::BACKQUOTE}, VKEY_OEM_3},
+      /* 135 */ {{0x0060, 0x007E, 0x0151, DomCode::BACKQUOTE}, VKEY_OEM_3},
       // grave accent, not sign, *
-      /* 135 */ {{0x0060, 0x00AC, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_8},
+      /* 136 */ {{0x0060, 0x00AC, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_8},
       // left curly bracket, *, *
-      /* 136 */ {{0x007B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
+      /* 137 */ {{0x007B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
       // vertical line, *, *
-      /* 137 */ {{0x007C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 138 */ {{0x007C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // right curly bracket, *, *
-      /* 138 */ {{0x007D, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_2},
+      /* 139 */ {{0x007D, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_2},
       // tilde, *, *
-      /* 139 */ {{0x007E, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 140 */ {{0x007E, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // inverted exclamation mark, *, *
-      /* 140 */ {{0x00A1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 141 */ {{0x00A1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // section sign, degree sign, *
-      /* 141 */ {{0x00A7, 0x00B0, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_2},
+      /* 142 */ {{0x00A7, 0x00B0, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_2},
       // section sign, vulgar fraction one half, *
-      /* 142 */ {{0x00A7, 0x00BD, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_5},
+      /* 143 */ {{0x00A7, 0x00BD, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_5},
       // section sign, *, *
-      /* 143 */ {{0x00A7, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
+      /* 144 */ {{0x00A7, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
       // section sign, *, *
-      /* 144 */ {{0x00A7, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
+      /* 145 */ {{0x00A7, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
       // section sign, *, *
-      /* 145 */ {{0x00A7, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 146 */ {{0x00A7, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // left-pointing double angle quotation mark, *, *
-      /* 146 */ {{0x00AB, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 147 */ {{0x00AB, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // left-pointing double angle quotation mark, *, *
-      /* 147 */ {{0x00AB, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_6},
+      /* 148 */ {{0x00AB, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_6},
       // soft hyphen, *, *
-      /* 148 */ {{0x00AD, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
+      /* 149 */ {{0x00AD, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
       // degree sign, *, *
-      /* 149 */ {{0x00B0, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_7},
+      /* 150 */ {{0x00B0, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_7},
       // degree sign, *, *
-      /* 150 */ {{0x00B0, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_2},
+      /* 151 */ {{0x00B0, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_2},
       // superscript two, *, *
-      /* 151 */ {{0x00B2, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
+      /* 152 */ {{0x00B2, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
       // micro sign, *, *
-      /* 152 */ {{0x00B5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 153 */ {{0x00B5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // masculine ordinal indicator, *, *
-      /* 153 */ {{0x00BA, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_5},
+      /* 154 */ {{0x00BA, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_OEM_5},
       // masculine ordinal indicator, *, *
-      /* 154 */ {{0x00BA, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 155 */ {{0x00BA, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // right-pointing double angle quotation mark, *, *
-      /* 155 */ {{0x00BB, 0x2460, 0x2461, DomCode::NONE}, VKEY_9},
+      /* 156 */ {{0x00BB, 0x2460, 0x2461, DomCode::NONE}, VKEY_9},
       // vulgar fraction one half, *, *
-      /* 156 */ {{0x00BD, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 157 */ {{0x00BD, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // inverted question mark, *, *
-      /* 157 */ {{0x00BF, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 158 */ {{0x00BF, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // sharp s, *, *
-      /* 158 */ {{0x00DF, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 159 */ {{0x00DF, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // a grave, degree sign, *
-      /* 159 */ {{0x00E0, 0x00B0, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 160 */ {{0x00E0, 0x00B0, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // a grave, a diaeresis, *
-      /* 160 */ {{0x00E0, 0x00E4, 0x2461, DomCode::QUOTE}, VKEY_OEM_5},
+      /* 161 */ {{0x00E0, 0x00E4, 0x2461, DomCode::QUOTE}, VKEY_OEM_5},
       // a grave, *, *
-      /* 161 */ {{0x00E0, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 162 */ {{0x00E0, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // a grave, *, *
-      /* 162 */ {{0x00E0, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
+      /* 163 */ {{0x00E0, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
       // a acute, *, *
-      /* 163 */ {{0x00E1, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 164 */ {{0x00E1, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // a acute, *, *
-      /* 164 */ {{0x00E1, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 165 */ {{0x00E1, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // a circumflex, *, *
-      /* 165 */ {{0x00E2, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 166 */ {{0x00E2, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // a circumflex, *, *
-      /* 166 */ {{0x00E2, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
+      /* 167 */ {{0x00E2, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
       // a diaeresis, A diaeresis, unmapped
-      /* 167 */ {{0x00E4, 0x00C4, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 168 */ {{0x00E4, 0x00C4, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // a diaeresis, A diaeresis, r caron
-      /* 168 */ {{0x00E4, 0x00C4, 0x0159, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 169 */ {{0x00E4, 0x00C4, 0x0159, DomCode::QUOTE}, VKEY_OEM_7},
       // a diaeresis, A diaeresis, S acute
-      /* 169 */ {{0x00E4, 0x00C4, 0x015A, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 170 */ {{0x00E4, 0x00C4, 0x015A, DomCode::QUOTE}, VKEY_OEM_7},
       // a diaeresis, a grave, *
-      /* 170 */ {{0x00E4, 0x00E0, 0x2461, DomCode::QUOTE}, VKEY_OEM_5},
+      /* 171 */ {{0x00E4, 0x00E0, 0x2461, DomCode::QUOTE}, VKEY_OEM_5},
       // a diaeresis, *, *
-      /* 171 */ {{0x00E4, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 172 */ {{0x00E4, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // a ring above, *, *
-      /* 172 */ {{0x00E5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 173 */ {{0x00E5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // ae, *, *
-      /* 173 */ {{0x00E6, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 174 */ {{0x00E6, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // ae, *, *
-      /* 174 */ {{0x00E6, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 175 */ {{0x00E6, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
       // c cedilla, C cedilla, unmapped
-      /* 175 */ {{0x00E7, 0x00C7, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 176 */ {{0x00E7, 0x00C7, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // c cedilla, C cedilla, Thorn
-      /* 176 */ {{0x00E7, 0x00C7, 0x00DE, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 177 */ {{0x00E7, 0x00C7, 0x00DE, DomCode::SEMICOLON}, VKEY_OEM_3},
       // c cedilla, *, *
-      /* 177 */ {{0x00E7, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
+      /* 178 */ {{0x00E7, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
       // c cedilla, *, *
-      /* 178 */ {{0x00E7, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 179 */ {{0x00E7, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // c cedilla, *, *
-      /* 179 */ {{0x00E7, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 180 */ {{0x00E7, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // c cedilla, *, *
-      /* 180 */ {{0x00E7, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_COMMA},
+      /* 181 */ {{0x00E7, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_COMMA},
       // c cedilla, *, *
-      /* 181 */ {{0x00E7, 0x2460, 0x2461, DomCode::DIGIT9}, VKEY_9},
+      /* 182 */ {{0x00E7, 0x2460, 0x2461, DomCode::DIGIT9}, VKEY_9},
       // c cedilla, *, *
-      /* 182 */ {{0x00E7, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 183 */ {{0x00E7, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // e grave, *, *
-      /* 183 */ {{0x00E8, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
+      /* 184 */ {{0x00E8, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
       // e grave, *, *
-      /* 184 */ {{0x00E8, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
+      /* 185 */ {{0x00E8, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
       // e grave, *, *
-      /* 185 */ {{0x00E8, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_3},
+      /* 186 */ {{0x00E8, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_3},
       // e acute, E acute, *
-      /* 186 */ {{0x00E9, 0x00C9, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 187 */ {{0x00E9, 0x00C9, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // e acute, o diaeresis, *
-      /* 187 */ {{0x00E9, 0x00F6, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_7},
+      /* 188 */ {{0x00E9, 0x00F6, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_7},
       // e acute, *, *
-      /* 188 */ {{0x00E9, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
+      /* 189 */ {{0x00E9, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
       // e acute, *, *
-      /* 189 */ {{0x00E9, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
+      /* 190 */ {{0x00E9, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
       // e acute, *, *
-      /* 190 */ {{0x00E9, 0x2460, 0x2461, DomCode::SLASH}, VKEY_OEM_2},
+      /* 191 */ {{0x00E9, 0x2460, 0x2461, DomCode::SLASH}, VKEY_OEM_2},
       // e circumflex, *, *
-      /* 191 */ {{0x00EA, 0x2460, 0x2461, DomCode::NONE}, VKEY_3},
+      /* 192 */ {{0x00EA, 0x2460, 0x2461, DomCode::NONE}, VKEY_3},
       // e diaeresis, *, *
-      /* 192 */ {{0x00EB, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
+      /* 193 */ {{0x00EB, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
       // i grave, *, *
-      /* 193 */ {{0x00EC, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 194 */ {{0x00EC, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // i acute, *, *
-      /* 194 */ {{0x00ED, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_0},
+      /* 195 */ {{0x00ED, 0x2460, 0x2461, DomCode::BACKQUOTE}, VKEY_0},
       // i acute, *, *
-      /* 195 */ {{0x00ED, 0x2460, 0x2461, DomCode::DIGIT9}, VKEY_9},
+      /* 196 */ {{0x00ED, 0x2460, 0x2461, DomCode::DIGIT9}, VKEY_9},
       // i circumflex, *, *
-      /* 196 */ {{0x00EE, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 197 */ {{0x00EE, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // eth, *, *
-      /* 197 */ {{0x00F0, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_6},
+      /* 198 */ {{0x00F0, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_6},
       // eth, *, *
-      /* 198 */ {{0x00F0, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_1},
+      /* 199 */ {{0x00F0, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_1},
       // n tilde, *, *
-      /* 199 */ {{0x00F1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
+      /* 200 */ {{0x00F1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
       // o grave, *, *
-      /* 200 */ {{0x00F2, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
+      /* 201 */ {{0x00F2, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
       // o acute, *, *
-      /* 201 */ {{0x00F3, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 202 */ {{0x00F3, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // o acute, *, *
-      /* 202 */ {{0x00F3, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
+      /* 203 */ {{0x00F3, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
       // o circumflex, *, *
-      /* 203 */ {{0x00F4, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
+      /* 204 */ {{0x00F4, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
       // o circumflex, *, *
-      /* 204 */ {{0x00F4, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 205 */ {{0x00F4, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // o tilde, *, *
-      /* 205 */ {{0x00F5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 206 */ {{0x00F5, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // o diaeresis, O diaeresis, unmapped
-      /* 206 */ {{0x00F6, 0x00D6, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 207 */ {{0x00F6, 0x00D6, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
       // o diaeresis, O diaeresis, T cedilla
-      /* 207 */ {{0x00F6, 0x00D6, 0x0162, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 208 */ {{0x00F6, 0x00D6, 0x0162, DomCode::SEMICOLON}, VKEY_OEM_3},
       // o diaeresis, e acute, *
-      /* 208 */ {{0x00F6, 0x00E9, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_7},
+      /* 209 */ {{0x00F6, 0x00E9, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_7},
       // o diaeresis, *, *
-      /* 209 */ {{0x00F6, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 210 */ {{0x00F6, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // o diaeresis, *, *
-      /* 210 */ {{0x00F6, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_OEM_3},
+      /* 211 */ {{0x00F6, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_OEM_3},
       // o diaeresis, *, *
-      /* 211 */ {{0x00F6, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
+      /* 212 */ {{0x00F6, 0x2460, 0x2461, DomCode::MINUS}, VKEY_OEM_PLUS},
       // division sign, *, *
-      /* 212 */ {{0x00F7, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 213 */ {{0x00F7, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // o stroke, *, *
-      /* 213 */ {{0x00F8, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 214 */ {{0x00F8, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // o stroke, *, *
-      /* 214 */ {{0x00F8, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 215 */ {{0x00F8, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
       // u grave, *, *
-      /* 215 */ {{0x00F9, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
+      /* 216 */ {{0x00F9, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
       // u grave, *, *
-      /* 216 */ {{0x00F9, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_3},
+      /* 217 */ {{0x00F9, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_3},
       // u acute, *, *
-      /* 217 */ {{0x00FA, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 218 */ {{0x00FA, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // u acute, *, *
-      /* 218 */ {{0x00FA, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 219 */ {{0x00FA, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // u diaeresis, U diaeresis, unmapped
-      /* 219 */ {{0x00FC, 0x00DC, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
+      /* 220 */ {{0x00FC, 0x00DC, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
       // u diaeresis, U diaeresis, unmapped
-      /* 220 */ {{0x00FC, 0x00DC, 0x2461, DomCode::MINUS}, VKEY_OEM_2},
+      /* 221 */ {{0x00FC, 0x00DC, 0x2461, DomCode::MINUS}, VKEY_OEM_2},
       // u diaeresis, U diaeresis, L stroke
-      /* 221 */ {{0x00FC, 0x00DC, 0x0141, DomCode::BRACKET_LEFT}, VKEY_OEM_3},
+      /* 222 */ {{0x00FC, 0x00DC, 0x0141, DomCode::BRACKET_LEFT}, VKEY_OEM_3},
       // u diaeresis, e grave, *
-      /* 222 */ {{0x00FC, 0x00E8, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
+      /* 223 */ {{0x00FC, 0x00E8, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_1},
       // u diaeresis, *, *
-      /* 223 */ {{0x00FC, 0x2460, 0x2461, DomCode::US_W}, VKEY_W},
+      /* 224 */ {{0x00FC, 0x2460, 0x2461, DomCode::US_W}, VKEY_W},
       // y acute, *, *
-      /* 224 */ {{0x00FD, 0x2460, 0x2461, DomCode::NONE}, VKEY_7},
+      /* 225 */ {{0x00FD, 0x2460, 0x2461, DomCode::NONE}, VKEY_7},
       // thorn, *, *
-      /* 225 */ {{0x00FE, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_MINUS},
+      /* 226 */ {{0x00FE, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_MINUS},
       // a macron, *, *
-      /* 226 */ {{0x0101, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_8},
+      /* 227 */ {{0x0101, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_8},
       // a breve, *, *
-      /* 227 */ {{0x0103, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 228 */ {{0x0103, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // a breve, *, *
-      /* 228 */ {{0x0103, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
+      /* 229 */ {{0x0103, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
       // a ogonek, *, *
-      /* 229 */ {{0x0105, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
+      /* 230 */ {{0x0105, 0x2460, 0x2461, DomCode::DIGIT1}, VKEY_1},
       // a ogonek, *, *
-      /* 230 */ {{0x0105, 0x2460, 0x2461, DomCode::US_Q}, VKEY_Q},
+      /* 231 */ {{0x0105, 0x2460, 0x2461, DomCode::US_Q}, VKEY_Q},
       // a ogonek, *, *
-      /* 231 */ {{0x0105, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 232 */ {{0x0105, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // c acute, *, *
-      /* 232 */ {{0x0107, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
+      /* 233 */ {{0x0107, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
       // c dot above, *, *
-      /* 233 */ {{0x010B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
+      /* 234 */ {{0x010B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
       // c caron, *, *
-      /* 234 */ {{0x010D, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_COMMA},
+      /* 235 */ {{0x010D, 0x2460, 0x2461, DomCode::COMMA}, VKEY_OEM_COMMA},
       // c caron, *, *
-      /* 235 */ {{0x010D, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
+      /* 236 */ {{0x010D, 0x2460, 0x2461, DomCode::DIGIT2}, VKEY_2},
       // c caron, *, *
-      /* 236 */ {{0x010D, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
+      /* 237 */ {{0x010D, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
       // c caron, *, *
-      /* 237 */ {{0x010D, 0x2460, 0x2461, DomCode::US_P}, VKEY_X},
+      /* 238 */ {{0x010D, 0x2460, 0x2461, DomCode::US_P}, VKEY_X},
       // c caron, *, *
-      /* 238 */ {{0x010D, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 239 */ {{0x010D, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // d stroke, *, *
-      /* 239 */ {{0x0111, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
+      /* 240 */ {{0x0111, 0x2460, 0x2461, DomCode::BRACKET_RIGHT}, VKEY_OEM_6},
       // d stroke, *, *
-      /* 240 */ {{0x0111, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
+      /* 241 */ {{0x0111, 0x2460, 0x2461, DomCode::DIGIT0}, VKEY_0},
       // e macron, *, *
-      /* 241 */ {{0x0113, 0x2460, 0x2461, DomCode::NONE}, VKEY_W},
+      /* 242 */ {{0x0113, 0x2460, 0x2461, DomCode::NONE}, VKEY_W},
       // e dot above, *, *
-      /* 242 */ {{0x0117, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
+      /* 243 */ {{0x0117, 0x2460, 0x2461, DomCode::DIGIT4}, VKEY_4},
       // e dot above, *, *
-      /* 243 */ {{0x0117, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
+      /* 244 */ {{0x0117, 0x2460, 0x2461, DomCode::QUOTE}, VKEY_OEM_7},
       // e ogonek, E ogonek, unmapped
-      /* 244 */ {{0x0119, 0x0118, 0x2461, DomCode::SLASH}, VKEY_OEM_MINUS},
+      /* 245 */ {{0x0119, 0x0118, 0x2461, DomCode::SLASH}, VKEY_OEM_MINUS},
       // e ogonek, E ogonek, n
-      /* 245 */ {{0x0119, 0x0118, 0x006E, DomCode::SLASH}, VKEY_OEM_2},
+      /* 246 */ {{0x0119, 0x0118, 0x006E, DomCode::SLASH}, VKEY_OEM_2},
       // e ogonek, *, *
-      /* 246 */ {{0x0119, 0x2460, 0x2461, DomCode::DIGIT3}, VKEY_3},
+      /* 247 */ {{0x0119, 0x2460, 0x2461, DomCode::DIGIT3}, VKEY_3},
       // e caron, *, *
-      /* 247 */ {{0x011B, 0x2460, 0x2461, DomCode::NONE}, VKEY_2},
+      /* 248 */ {{0x011B, 0x2460, 0x2461, DomCode::NONE}, VKEY_2},
       // g breve, *, *
-      /* 248 */ {{0x011F, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 249 */ {{0x011F, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // g dot above, *, *
-      /* 249 */ {{0x0121, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 250 */ {{0x0121, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // h stroke, *, *
-      /* 250 */ {{0x0127, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 251 */ {{0x0127, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // i macron, *, *
-      /* 251 */ {{0x012B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 252 */ {{0x012B, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // i ogonek, I ogonek, unmapped
-      /* 252 */ {{0x012F, 0x012E, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 253 */ {{0x012F, 0x012E, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // i ogonek, *, *
-      /* 253 */ {{0x012F, 0x2460, 0x2461, DomCode::DIGIT5}, VKEY_5},
+      /* 254 */ {{0x012F, 0x2460, 0x2461, DomCode::DIGIT5}, VKEY_5},
       // dotless i, *, *
-      /* 254 */ {{0x0131, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
+      /* 255 */ {{0x0131, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
       // k cedilla, *, *
-      /* 255 */ {{0x0137, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 256 */ {{0x0137, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // l cedilla, *, *
-      /* 256 */ {{0x013C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_2},
+      /* 257 */ {{0x013C, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_2},
       // l caron, *, *
-      /* 257 */ {{0x013E, 0x2460, 0x2461, DomCode::NONE}, VKEY_2},
+      /* 258 */ {{0x013E, 0x2460, 0x2461, DomCode::NONE}, VKEY_2},
       // l stroke, *, *
-      /* 258 */ {{0x0142, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
+      /* 259 */ {{0x0142, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_2},
       // l stroke, *, *
-      /* 259 */ {{0x0142, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 260 */ {{0x0142, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // n cedilla, *, *
-      /* 260 */ {{0x0146, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 261 */ {{0x0146, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // n caron, *, *
-      /* 261 */ {{0x0148, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 262 */ {{0x0148, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // o double acute, *, *
-      /* 262 */ {{0x0151, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 263 */ {{0x0151, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // r caron, *, *
-      /* 263 */ {{0x0159, 0x2460, 0x2461, DomCode::NONE}, VKEY_5},
+      /* 264 */ {{0x0159, 0x2460, 0x2461, DomCode::NONE}, VKEY_5},
       // s cedilla, *, *
-      /* 264 */ {{0x015F, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_PERIOD},
+      /* 265 */ {{0x015F, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_PERIOD},
       // s cedilla, *, *
-      /* 265 */ {{0x015F, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 266 */ {{0x015F, 0x2460, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_1},
       // s caron, *, *
-      /* 266 */ {{0x0161, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 267 */ {{0x0161, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // s caron, *, *
-      /* 267 */ {{0x0161, 0x2460, 0x2461, DomCode::DIGIT3}, VKEY_3},
+      /* 268 */ {{0x0161, 0x2460, 0x2461, DomCode::DIGIT3}, VKEY_3},
       // s caron, *, *
-      /* 268 */ {{0x0161, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
+      /* 269 */ {{0x0161, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
       // s caron, *, *
-      /* 269 */ {{0x0161, 0x2460, 0x2461, DomCode::US_A}, VKEY_OEM_1},
+      /* 270 */ {{0x0161, 0x2460, 0x2461, DomCode::US_A}, VKEY_OEM_1},
       // s caron, *, *
-      /* 270 */ {{0x0161, 0x2460, 0x2461, DomCode::US_F}, VKEY_F},
+      /* 271 */ {{0x0161, 0x2460, 0x2461, DomCode::US_F}, VKEY_F},
       // s caron, *, *
-      /* 271 */ {{0x0161, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_PERIOD},
+      /* 272 */ {{0x0161, 0x2460, 0x2461, DomCode::PERIOD}, VKEY_OEM_PERIOD},
       // t cedilla, *, *
-      /* 272 */ {{0x0163, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
+      /* 273 */ {{0x0163, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_7},
       // t caron, *, *
-      /* 273 */ {{0x0165, 0x2460, 0x2461, DomCode::NONE}, VKEY_5},
+      /* 274 */ {{0x0165, 0x2460, 0x2461, DomCode::NONE}, VKEY_5},
       // u macron, *, *
-      /* 274 */ {{0x016B, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
+      /* 275 */ {{0x016B, 0x2460, 0x2461, DomCode::DIGIT8}, VKEY_8},
       // u macron, *, *
-      /* 275 */ {{0x016B, 0x2460, 0x2461, DomCode::US_Q}, VKEY_Q},
+      /* 276 */ {{0x016B, 0x2460, 0x2461, DomCode::US_Q}, VKEY_Q},
       // u macron, *, *
-      /* 276 */ {{0x016B, 0x2460, 0x2461, DomCode::US_X}, VKEY_X},
+      /* 277 */ {{0x016B, 0x2460, 0x2461, DomCode::US_X}, VKEY_X},
       // u ring above, *, *
-      /* 277 */ {{0x016F, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
+      /* 278 */ {{0x016F, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_1},
       // u double acute, *, *
-      /* 278 */ {{0x0171, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
+      /* 279 */ {{0x0171, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_5},
       // u ogonek, U ogonek, unmapped
-      /* 279 */ {{0x0173, 0x0172, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
+      /* 280 */ {{0x0173, 0x0172, 0x2461, DomCode::SEMICOLON}, VKEY_OEM_3},
       // u ogonek, U ogonek, T cedilla
-      /* 280 */ {{0x0173, 0x0172, 0x0162, DomCode::SEMICOLON}, VKEY_OEM_1},
+      /* 281 */ {{0x0173, 0x0172, 0x0162, DomCode::SEMICOLON}, VKEY_OEM_1},
       // u ogonek, *, *
-      /* 281 */ {{0x0173, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
+      /* 282 */ {{0x0173, 0x2460, 0x2461, DomCode::DIGIT7}, VKEY_7},
       // z dot above, *, *
-      /* 282 */ {{0x017C, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 283 */ {{0x017C, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // z dot above, *, *
-      /* 283 */ {{0x017C, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
+      /* 284 */ {{0x017C, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_OEM_4},
       // z caron, *, *
-      /* 284 */ {{0x017E, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
+      /* 285 */ {{0x017E, 0x2460, 0x2461, DomCode::BACKSLASH}, VKEY_OEM_5},
       // z caron, *, *
-      /* 285 */ {{0x017E, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_Y},
+      /* 286 */ {{0x017E, 0x2460, 0x2461, DomCode::BRACKET_LEFT}, VKEY_Y},
       // z caron, *, *
-      /* 286 */ {{0x017E, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
+      /* 287 */ {{0x017E, 0x2460, 0x2461, DomCode::DIGIT6}, VKEY_6},
       // z caron, *, *
-      /* 287 */ {{0x017E, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
+      /* 288 */ {{0x017E, 0x2460, 0x2461, DomCode::EQUAL}, VKEY_OEM_PLUS},
       // z caron, *, *
-      /* 288 */ {{0x017E, 0x2460, 0x2461, DomCode::US_W}, VKEY_W},
+      /* 289 */ {{0x017E, 0x2460, 0x2461, DomCode::US_W}, VKEY_W},
       // o horn, *, *
-      /* 289 */ {{0x01A1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 290 */ {{0x01A1, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // u horn, *, *
-      /* 290 */ {{0x01B0, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
+      /* 291 */ {{0x01B0, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_4},
       // z stroke, *, *
-      /* 291 */ {{0x01B6, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
+      /* 292 */ {{0x01B6, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_6},
       // schwa, *, *
-      /* 292 */ {{0x0259, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
+      /* 293 */ {{0x0259, 0x2460, 0x2461, DomCode::NONE}, VKEY_OEM_3},
 
       // Simple alphanumeric cases.
-      /* 293 */ {{'a', 'A', '?', DomCode::NONE}, VKEY_A},
-      /* 294 */ {{'z', 'Z', '!', DomCode::NONE}, VKEY_Z},
-      /* 295 */ {{'9', '(', '+', DomCode::NONE}, VKEY_9},
-      /* 296 */ {{'0', ')', '-', DomCode::NONE}, VKEY_0},
+      /* 294 */ {{'a', 'A', '?', DomCode::NONE}, VKEY_A},
+      /* 295 */ {{'z', 'Z', '!', DomCode::NONE}, VKEY_Z},
+      /* 296 */ {{'9', '(', '+', DomCode::NONE}, VKEY_9},
+      /* 297 */ {{'0', ')', '-', DomCode::NONE}, VKEY_0},
+
   };
 
   for (size_t i = 0; i < base::size(kVkeyTestCase); ++i) {
diff --git a/ui/gl/DEPS b/ui/gl/DEPS
index ca1a801..a135d3b 100644
--- a/ui/gl/DEPS
+++ b/ui/gl/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+cc/base",
+  "+components/viz/common/resources/resource_format.h",
   "+mojo/public/cpp/bindings",
   "+third_party/khronos",
   "+third_party/libsync",
diff --git a/ui/gl/direct_composition_surface_win_unittest.cc b/ui/gl/direct_composition_surface_win_unittest.cc
index 63fc30a..7502a02 100644
--- a/ui/gl/direct_composition_surface_win_unittest.cc
+++ b/ui/gl/direct_composition_surface_win_unittest.cc
@@ -1213,8 +1213,9 @@
   ASSERT_TRUE(front_buffer_texture);
 
   auto front_buffer_image = base::MakeRefCounted<GLImageD3D>(
-      swap_chain_size, GL_BGRA_EXT, GL_UNSIGNED_BYTE, front_buffer_texture,
-      swap_chain);
+      swap_chain_size, GL_BGRA_EXT, GL_UNSIGNED_BYTE,
+      gfx::ColorSpace::CreateSRGB(), front_buffer_texture,
+      /*array_slice=*/0, /*plane_index=*/0, swap_chain);
   ASSERT_TRUE(front_buffer_image->Initialize());
 
   Microsoft::WRL::ComPtr<ID3D11Texture2D> back_buffer_texture;
diff --git a/ui/gl/gl_image_d3d.cc b/ui/gl/gl_image_d3d.cc
index 976ab720..75fc087c 100644
--- a/ui/gl/gl_image_d3d.cc
+++ b/ui/gl/gl_image_d3d.cc
@@ -11,49 +11,30 @@
 #ifndef EGL_ANGLE_image_d3d11_texture
 #define EGL_D3D11_TEXTURE_ANGLE 0x3484
 #define EGL_TEXTURE_INTERNAL_FORMAT_ANGLE 0x345D
+#define EGL_D3D11_TEXTURE_PLANE_ANGLE 0x3492
+#define EGL_D3D11_TEXTURE_ARRAY_SLICE_ANGLE 0x3493
 #endif /* EGL_ANGLE_image_d3d11_texture */
 
 namespace gl {
 
-namespace {
-
-bool ValidGLInternalFormat(unsigned internal_format) {
-  switch (internal_format) {
-    case GL_RGB:
-    case GL_RGBA:
-    case GL_BGRA_EXT:
-      return true;
-    default:
-      return false;
-  }
-}
-
-bool ValidGLDataType(unsigned data_type) {
-  switch (data_type) {
-    case GL_UNSIGNED_BYTE:
-    case GL_HALF_FLOAT_OES:
-      return true;
-    default:
-      return false;
-  }
-}
-
-}  // namespace
-
 GLImageD3D::GLImageD3D(const gfx::Size& size,
                        unsigned internal_format,
                        unsigned data_type,
+                       const gfx::ColorSpace& color_space,
                        Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
+                       size_t array_slice,
+                       size_t plane_index,
                        Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain)
     : GLImage(),
       size_(size),
       internal_format_(internal_format),
       data_type_(data_type),
       texture_(std::move(texture)),
+      array_slice_(array_slice),
+      plane_index_(plane_index),
       swap_chain_(std::move(swap_chain)) {
+  GLImage::SetColorSpace(color_space);
   DCHECK(texture_);
-  DCHECK(ValidGLInternalFormat(internal_format_));
-  DCHECK(ValidGLDataType(data_type_));
 }
 
 GLImageD3D::~GLImageD3D() {
@@ -69,7 +50,12 @@
 bool GLImageD3D::Initialize() {
   DCHECK_EQ(egl_image_, EGL_NO_IMAGE_KHR);
   const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE,
-                            GetInternalFormat(), EGL_NONE};
+                            internal_format_,
+                            EGL_D3D11_TEXTURE_ARRAY_SLICE_ANGLE,
+                            array_slice_,
+                            EGL_D3D11_TEXTURE_PLANE_ANGLE,
+                            plane_index_,
+                            EGL_NONE};
   egl_image_ =
       eglCreateImageKHR(GLSurfaceEGL::GetHardwareDisplay(), EGL_NO_CONTEXT,
                         EGL_D3D11_TEXTURE_ANGLE,
diff --git a/ui/gl/gl_image_d3d.h b/ui/gl/gl_image_d3d.h
index 6c5b2166..fa62f42 100644
--- a/ui/gl/gl_image_d3d.h
+++ b/ui/gl/gl_image_d3d.h
@@ -22,13 +22,19 @@
   // |internal_format| and |data_type| are passed to ANGLE and used for GL
   // operations.  |internal_format| may be different from the internal format
   // associated with the DXGI_FORMAT of the texture (e.g. RGB instead of
-  // BGRA_EXT for DXGI_FORMAT_B8G8R8A8_UNORM).  |data_type| should match the
-  // data type accociated with the DXGI_FORMAT of the texture.
+  // BGRA_EXT for DXGI_FORMAT_B8G8R8A8_UNORM). |data_type| should match the data
+  // type accociated with the DXGI_FORMAT of the texture.  |array_slice| is used
+  // when the |texture| is a D3D11 texture array, and |plane_index| is used for
+  // specifying the plane to bind to for multi-planar YUV textures.
+  // See EGL_ANGLE_d3d_texture_client_buffer spec for format restrictions.
   GLImageD3D(const gfx::Size& size,
              unsigned internal_format,
              unsigned data_type,
+             const gfx::ColorSpace& color_space,
              Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
-             Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain);
+             size_t array_slice = 0,
+             size_t plane_index = 0,
+             Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = nullptr);
 
   // Safe downcast. Returns nullptr on failure.
   static GLImageD3D* FromGLImage(GLImage* image);
@@ -62,20 +68,27 @@
   const Microsoft::WRL::ComPtr<ID3D11Texture2D>& texture() const {
     return texture_;
   }
-
   const Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain() const {
     return swap_chain_;
   }
+  size_t array_slice() const { return array_slice_; }
+  size_t plane_index() const { return plane_index_; }
+
+ protected:
+  const gfx::Size size_;
+  const unsigned internal_format_;  // GLenum
+  const unsigned data_type_;        // GLenum
+
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture_;
+  const size_t array_slice_;
+  const size_t plane_index_;
+
+  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;
 
  private:
   ~GLImageD3D() override;
 
-  const gfx::Size size_;
-  const unsigned internal_format_;  // GLenum
-  const unsigned data_type_;        // GLenum
-  void* egl_image_ = nullptr;       // EGLImageKHR
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture_;
-  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;
+  void* egl_image_ = nullptr;  // EGLImageKHR
 
   DISALLOW_COPY_AND_ASSIGN(GLImageD3D);
 };
diff --git a/ui/gl/gl_image_d3d_unittest.cc b/ui/gl/gl_image_d3d_unittest.cc
index febd42ee..4b047f4d 100644
--- a/ui/gl/gl_image_d3d_unittest.cc
+++ b/ui/gl/gl_image_d3d_unittest.cc
@@ -52,7 +52,8 @@
     EXPECT_TRUE(SUCCEEDED(hr));
 
     auto image = base::MakeRefCounted<GLImageD3D>(
-        size, internal_format, data_type, std::move(d3d11_texture), nullptr);
+        size, internal_format, data_type, gfx::ColorSpace::CreateSRGB(),
+        std::move(d3d11_texture));
     EXPECT_TRUE(image->Initialize());
     return image;
   }
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index 5c36e7e9..4067ff7 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -638,23 +638,28 @@
 }
 
 bool SwapChainPresenter::TryPresentToDecodeSwapChain(
-    GLImageDXGI* nv12_image,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
+    unsigned array_slice,
+    const gfx::ColorSpace& color_space,
     const gfx::Rect& content_rect,
-    const gfx::Size& swap_chain_size) {
+    const gfx::Size& swap_chain_size,
+    DXGI_FORMAT swap_chain_format) {
   if (ShouldUseVideoProcessorScaling())
     return false;
 
   auto not_used_reason = DecodeSwapChainNotUsedReason::kFailedToPresent;
 
   bool nv12_supported =
+      (swap_chain_format == DXGI_FORMAT_NV12) &&
       (DXGI_FORMAT_NV12 ==
        DirectCompositionSurfaceWin::GetOverlayFormatUsedForSDR());
   // TODO(sunnyps): Try using decode swap chain for uploaded video images.
-  if (nv12_image && nv12_supported && !failed_to_present_decode_swapchain_) {
+  if (texture && nv12_supported && !failed_to_present_decode_swapchain_) {
     D3D11_TEXTURE2D_DESC texture_desc = {};
-    nv12_image->texture()->GetDesc(&texture_desc);
+    texture->GetDesc(&texture_desc);
 
-    bool is_decoder_texture = texture_desc.BindFlags & D3D11_BIND_DECODER;
+    bool is_decoder_texture = (texture_desc.Format == DXGI_FORMAT_NV12) &&
+                              (texture_desc.BindFlags & D3D11_BIND_DECODER);
 
     // Decode swap chains do not support shared resources.
     // TODO(sunnyps): Find a workaround for when the decoder moves to its own
@@ -688,8 +693,10 @@
 
     if (is_decoder_texture && !is_shared_texture && !is_unitary_texture_array &&
         is_overlay_supported_transform) {
-      if (PresentToDecodeSwapChain(nv12_image, content_rect, swap_chain_size))
+      if (PresentToDecodeSwapChain(texture, array_slice, color_space,
+                                   content_rect, swap_chain_size)) {
         return true;
+      }
       ReleaseSwapChainResources();
       failed_to_present_decode_swapchain_ = true;
       not_used_reason = DecodeSwapChainNotUsedReason::kFailedToPresent;
@@ -704,7 +711,7 @@
     } else if (!is_overlay_supported_transform) {
       not_used_reason = DecodeSwapChainNotUsedReason::kIncompatibleTransform;
     }
-  } else if (!nv12_image) {
+  } else if (!texture) {
     not_used_reason = DecodeSwapChainNotUsedReason::kSoftwareFrame;
   } else if (!nv12_supported) {
     not_used_reason = DecodeSwapChainNotUsedReason::kNv12NotSupported;
@@ -718,7 +725,9 @@
 }
 
 bool SwapChainPresenter::PresentToDecodeSwapChain(
-    GLImageDXGI* nv12_image,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
+    unsigned array_slice,
+    const gfx::ColorSpace& color_space,
     const gfx::Rect& content_rect,
     const gfx::Size& swap_chain_size) {
   DCHECK(!swap_chain_size.IsEmpty());
@@ -728,7 +737,7 @@
                swap_chain_size.ToString());
 
   Microsoft::WRL::ComPtr<IDXGIResource> decode_resource;
-  nv12_image->texture().As(&decode_resource);
+  texture.As(&decode_resource);
   DCHECK(decode_resource);
 
   if (!decode_swap_chain_ || decode_resource_ != decode_resource) {
@@ -790,10 +799,6 @@
 
     content_visual_->SetContent(decode_surface_.Get());
     layer_tree_->SetNeedsRebuildVisualTree();
-  } else if (last_presented_images_[kNV12ImageIndex] == nv12_image &&
-             swap_chain_size_ == swap_chain_size) {
-    // Early out if we're presenting the same image again.
-    return true;
   }
 
   RECT source_rect = content_rect.ToRECT();
@@ -804,10 +809,6 @@
   RECT target_rect = gfx::Rect(swap_chain_size).ToRECT();
   decode_swap_chain_->SetTargetRect(&target_rect);
 
-  gfx::ColorSpace color_space = nv12_image->color_space();
-  if (!color_space.IsValid())
-    color_space = gfx::ColorSpace::CreateREC709();
-
   // TODO(sunnyps): Move this to gfx::ColorSpaceWin helper where we can access
   // internal color space state and do a better job.
   // Common color spaces have primaries and transfer function similar to BT 709
@@ -815,7 +816,8 @@
   int color_space_flags = DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_BT709;
   // Proper Rec 709 and 601 have limited or nominal color range.
   if (color_space == gfx::ColorSpace::CreateREC709() ||
-      color_space == gfx::ColorSpace::CreateREC601()) {
+      color_space == gfx::ColorSpace::CreateREC601() ||
+      !color_space.IsValid()) {
     color_space_flags |= DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_NOMINAL_RANGE;
   }
   // xvYCC allows colors outside nominal range to encode negative colors that
@@ -827,8 +829,7 @@
       static_cast<DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS>(color_space_flags));
 
   UINT present_flags = DXGI_PRESENT_USE_DURATION;
-  HRESULT hr =
-      decode_swap_chain_->PresentBuffer(nv12_image->level(), 1, present_flags);
+  HRESULT hr = decode_swap_chain_->PresentBuffer(array_slice, 1, present_flags);
   // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
   // that the window is occluded and we can stop rendering.
   if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
@@ -836,8 +837,6 @@
     return false;
   }
 
-  last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
-  last_presented_images_[kNV12ImageIndex] = nv12_image;
   swap_chain_size_ = swap_chain_size;
   if (swap_chain_format_ == DXGI_FORMAT_NV12) {
     frames_since_color_space_change_++;
@@ -856,14 +855,20 @@
     const ui::DCRendererLayerParams& params) {
   GLImageDXGI* image_dxgi =
       GLImageDXGI::FromGLImage(params.images[kNV12ImageIndex].get());
+  GLImageD3D* image_d3d =
+      GLImageD3D::FromGLImage(params.images[kNV12ImageIndex].get());
+
   GLImageMemory* y_image_memory =
       GLImageMemory::FromGLImage(params.images[kYPlaneImageIndex].get());
   GLImageMemory* uv_image_memory =
       GLImageMemory::FromGLImage(params.images[kUVPlaneImageIndex].get());
+
   GLImageD3D* swap_chain_image =
       GLImageD3D::FromGLImage(params.images[kSwapChainImageIndex].get());
+  if (swap_chain_image && !swap_chain_image->swap_chain())
+    swap_chain_image = nullptr;
 
-  if (!image_dxgi && (!y_image_memory || !uv_image_memory) &&
+  if (!image_dxgi && !image_d3d && (!y_image_memory || !uv_image_memory) &&
       !swap_chain_image) {
     DLOG(ERROR) << "Video GLImages are missing";
     ReleaseSwapChainResources();
@@ -873,7 +878,8 @@
     return true;
   }
 
-  if (image_dxgi && !image_dxgi->texture()) {
+  if ((image_dxgi && !image_dxgi->texture()) ||
+      (image_d3d && !image_d3d->texture())) {
     // We can't proceed if |image_dxgi| has no underlying d3d11 texture.  It's
     // unclear how we get into this state, but we do observe crashes due to it.
     // Just stop here instead, and render incorrectly.
@@ -883,11 +889,14 @@
     return true;
   }
 
-  std::string image_type = "software video frame";
-  if (image_dxgi)
-    image_type = "hardware video frame";
-  if (swap_chain_image)
+  std::string image_type;
+  if (swap_chain_image) {
     image_type = "swap chain";
+  } else if (image_d3d || image_dxgi) {
+    image_type = "hardware video frame";
+  } else {
+    image_type = "software video frame";
+  }
 
   gfx::Transform transform = params.transform;
   gfx::Rect clip_rect = params.clip_rect.value_or(gfx::Rect());
@@ -904,12 +913,29 @@
   TRACE_EVENT2("gpu", "SwapChainPresenter::PresentToSwapChain", "image_type",
                image_type, "swap_chain_size", swap_chain_size.ToString());
 
-  bool content_is_hdr = image_dxgi && image_dxgi->color_space().IsHDR();
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
+  unsigned input_level = 0u;
+  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
+  gfx::ColorSpace input_color_space;
+  if (image_dxgi) {
+    input_texture = image_dxgi->texture();
+    input_level = image_dxgi->level();
+    // Keyed mutex may not exist.
+    keyed_mutex = image_dxgi->keyed_mutex();
+    input_color_space = image_dxgi->color_space();
+  } else if (image_d3d) {
+    input_texture = image_d3d->texture();
+    input_level = image_d3d->array_slice();
+    input_color_space = image_d3d->color_space();
+  }
+  if (!input_color_space.IsValid())
+    input_color_space = gfx::ColorSpace::CreateREC709();
+
+  bool content_is_hdr = input_color_space.IsHDR();
   // Do not create a swap chain if swap chain size will be empty.
   if (swap_chain_size.IsEmpty()) {
     swap_chain_size_ = swap_chain_size;
     if (swap_chain_) {
-      last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
       ReleaseSwapChainResources();
       content_visual_->SetContent(nullptr);
       layer_tree_->SetNeedsRebuildVisualTree();
@@ -922,20 +948,16 @@
   // Swap chain image already has a swap chain that's presented by the client
   // e.g. for webgl/canvas low-latency/desynchronized mode.
   if (swap_chain_image) {
+    DCHECK(swap_chain_image->swap_chain());
     content_visual_->SetContent(swap_chain_image->swap_chain().Get());
-    if (last_presented_images_[kSwapChainImageIndex] != swap_chain_image) {
-      last_presented_images_ = params.images;
+    if (last_presented_images_ != params.images) {
       ReleaseSwapChainResources();
+      last_presented_images_ = params.images;
       layer_tree_->SetNeedsRebuildVisualTree();
     }
     return true;
   }
 
-  if (TryPresentToDecodeSwapChain(image_dxgi, params.content_rect,
-                                  swap_chain_size)) {
-    return true;
-  }
-
   bool swap_chain_resized = swap_chain_size_ != swap_chain_size;
 
   DXGI_FORMAT swap_chain_format =
@@ -944,6 +966,21 @@
   bool toggle_protected_video =
       protected_video_type_ != params.protected_video_type;
 
+  if (swap_chain_ && !swap_chain_resized && !swap_chain_format_changed &&
+      !toggle_protected_video && last_presented_images_ == params.images) {
+    // The swap chain is presenting the same images as last swap, which means
+    // that the images were never returned to the video decoder and should
+    // have the same contents as last time. It shouldn't need to be redrawn.
+    return true;
+  }
+
+  if (TryPresentToDecodeSwapChain(input_texture, input_level, input_color_space,
+                                  params.content_rect, swap_chain_size,
+                                  swap_chain_format)) {
+    last_presented_images_ = params.images;
+    return true;
+  }
+
   // Try reallocating swap chain if resizing fails.
   if (!swap_chain_ || swap_chain_resized || swap_chain_format_changed ||
       toggle_protected_video) {
@@ -954,22 +991,9 @@
     }
     content_visual_->SetContent(swap_chain_.Get());
     layer_tree_->SetNeedsRebuildVisualTree();
-  } else if (last_presented_images_ == params.images) {
-    // The swap chain is presenting the same images as last swap, which means
-    // that the images were never returned to the video decoder and should
-    // have the same contents as last time. It shouldn't need to be redrawn.
-    return true;
   }
-  last_presented_images_ = params.images;
 
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
-  UINT input_level;
-  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
-  if (image_dxgi) {
-    input_texture = image_dxgi->texture();
-    input_level = static_cast<UINT>(image_dxgi->level());
-    // Keyed mutex may not exist.
-    keyed_mutex = image_dxgi->keyed_mutex();
+  if (input_texture) {
     staging_texture_.Reset();
     copy_texture_.Reset();
   } else {
@@ -1060,6 +1084,7 @@
     DLOG(ERROR) << "Present failed with error 0x" << std::hex << hr;
     return false;
   }
+  last_presented_images_ = params.images;
   frames_since_color_space_change_++;
   RecordPresentationStatistics();
   return true;
@@ -1289,6 +1314,7 @@
 }
 
 void SwapChainPresenter::ReleaseSwapChainResources() {
+  last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
   output_view_.Reset();
   swap_chain_.Reset();
   decode_surface_.Reset();
diff --git a/ui/gl/swap_chain_presenter.h b/ui/gl/swap_chain_presenter.h
index d6b7faf..20858ee 100644
--- a/ui/gl/swap_chain_presenter.h
+++ b/ui/gl/swap_chain_presenter.h
@@ -19,7 +19,6 @@
 
 namespace gl {
 class DCLayerTree;
-class GLImageDXGI;
 class GLImageMemory;
 
 // SwapChainPresenter holds a swap chain, direct composition visuals, and other
@@ -161,14 +160,20 @@
   // Try presenting to a decode swap chain based on various conditions such as
   // global state (e.g. finch, NV12 support), texture flags, and transform.
   // Returns true on success.  See PresentToDecodeSwapChain() for more info.
-  bool TryPresentToDecodeSwapChain(GLImageDXGI* nv12_image,
-                                   const gfx::Rect& content_rect,
-                                   const gfx::Size& swap_chain_size);
+  bool TryPresentToDecodeSwapChain(
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
+      unsigned array_slice,
+      const gfx::ColorSpace& color_space,
+      const gfx::Rect& content_rect,
+      const gfx::Size& swap_chain_size,
+      DXGI_FORMAT swap_chain_format);
 
   // Present to a decode swap chain created from compatible video decoder
   // buffers using given |nv12_image| with destination size |swap_chain_size|.
   // Returns true on success.
-  bool PresentToDecodeSwapChain(GLImageDXGI* nv12_image,
+  bool PresentToDecodeSwapChain(Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
+                                unsigned array_slice,
+                                const gfx::ColorSpace& color_space,
                                 const gfx::Rect& content_rect,
                                 const gfx::Size& swap_chain_size);
 
diff --git a/ui/ozone/platform/wayland/fuzzer/wayland_buffer_fuzzer.cc b/ui/ozone/platform/wayland/fuzzer/wayland_buffer_fuzzer.cc
index f0326e4e..7072d78 100644
--- a/ui/ozone/platform/wayland/fuzzer/wayland_buffer_fuzzer.cc
+++ b/ui/ozone/platform/wayland/fuzzer/wayland_buffer_fuzzer.cc
@@ -110,7 +110,7 @@
       DRM_FORMAT_NV12,        DRM_FORMAT_YVU420};
 
   wl::TestWaylandServerThread server;
-  CHECK(server.Start(6));
+  CHECK(server.Start({.shell_version = wl::ShellVersion::kV6}));
 
   std::unique_ptr<ui::WaylandConnection> connection =
       std::make_unique<ui::WaylandConnection>();
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
index 7c24257..0567e79 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
@@ -38,6 +38,7 @@
 using ::testing::_;
 using ::testing::Expectation;
 using ::testing::SaveArg;
+using ::testing::Values;
 
 namespace ui {
 
@@ -791,9 +792,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandSurfaceFactoryTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandSurfaceFactoryTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc b/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
index 43aadb4..9ba1b62 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
@@ -31,6 +31,7 @@
 
 using testing::_;
 using testing::Mock;
+using testing::Values;
 
 namespace ui {
 
@@ -253,10 +254,12 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandClipboardTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandClipboardTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_connection_unittest.cc b/ui/ozone/platform/wayland/host/wayland_connection_unittest.cc
index d071f28..fa467468 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection_unittest.cc
@@ -16,15 +16,11 @@
 
 namespace ui {
 
-namespace {
-constexpr uint32_t kXdgVersionStable = 7;
-}
-
 TEST(WaylandConnectionTest, Ping) {
   base::test::SingleThreadTaskEnvironment task_environment(
       base::test::SingleThreadTaskEnvironment::MainThreadType::UI);
   wl::TestWaylandServerThread server;
-  ASSERT_TRUE(server.Start(kXdgVersionStable));
+  ASSERT_TRUE(server.Start({.shell_version = wl::ShellVersion::kStable}));
   WaylandConnection connection;
   ASSERT_TRUE(connection.Initialize());
   connection.event_source()->UseSingleThreadedPollingForTesting();
diff --git a/ui/ozone/platform/wayland/host/wayland_cursor_factory_unittest.cc b/ui/ozone/platform/wayland/host/wayland_cursor_factory_unittest.cc
index 9e7dbe5..8b33f58a 100644
--- a/ui/ozone/platform/wayland/host/wayland_cursor_factory_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_cursor_factory_unittest.cc
@@ -15,6 +15,8 @@
 
 namespace ui {
 
+using ::testing::Values;
+
 namespace {
 
 // Overrides WaylandCursorFactory::GetCursorFromTheme() to pretend that cursors
@@ -128,10 +130,12 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandCursorFactoryTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandCursorFactoryTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index 23a0aed..53c0ce2 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -47,6 +47,7 @@
 using mojom::DragOperation;
 using ::testing::_;
 using ::testing::Mock;
+using ::testing::Values;
 
 constexpr char kSampleTextForDragAndDrop[] =
     "This is a sample text for drag-and-drop.";
@@ -753,10 +754,12 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandDataDragControllerTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandDataDragControllerTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
index fe57fe1..b2606b5 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
@@ -13,6 +13,7 @@
 #include "ui/ozone/test/mock_platform_window_delegate.h"
 
 using ::testing::_;
+using ::testing::Values;
 
 namespace ui {
 
@@ -110,9 +111,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandEventSourceTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandEventSourceTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc b/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
index ba1e5b0..d73061c 100644
--- a/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_input_method_context_unittest.cc
@@ -20,6 +20,7 @@
 
 using ::testing::_;
 using ::testing::SaveArg;
+using ::testing::Values;
 
 namespace ui {
 
@@ -136,9 +137,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandInputMethodContextTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandInputMethodContextTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc b/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
index 91c1d3b..a24c196 100644
--- a/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
@@ -25,10 +25,11 @@
 #include "ui/events/keycodes/scoped_xkb.h"  // nogncheck
 #endif
 
+namespace ui {
+
 using ::testing::_;
 using ::testing::SaveArg;
-
-namespace ui {
+using ::testing::Values;
 
 class WaylandKeyboardTest : public WaylandTest {
  public:
@@ -486,9 +487,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandKeyboardTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandKeyboardTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
index a7ecd69..818a8a55a 100644
--- a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc
@@ -29,6 +29,7 @@
 using ::testing::Mock;
 using ::testing::Ne;
 using ::testing::SaveArg;
+using ::testing::Values;
 
 namespace ui {
 
@@ -540,9 +541,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandPointerTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandPointerTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
index 300b6a79..f643456 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
@@ -25,6 +25,8 @@
 
 namespace ui {
 
+using ::testing::Values;
+
 namespace {
 
 constexpr uint32_t kNumberOfDisplays = 1;
@@ -737,15 +739,19 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandScreenTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandScreenTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          LazilyConfiguredScreenTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          LazilyConfiguredScreenTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_touch_unittest.cc b/ui/ozone/platform/wayland/host/wayland_touch_unittest.cc
index 4d1c4fe..e560947 100644
--- a/ui/ozone/platform/wayland/host/wayland_touch_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_touch_unittest.cc
@@ -18,6 +18,7 @@
 
 using ::testing::_;
 using ::testing::SaveArg;
+using ::testing::Values;
 
 namespace ui {
 
@@ -158,9 +159,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandTouchTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandTouchTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index 71ed0e52..e4b83ef 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -40,6 +40,7 @@
 
 using testing::_;
 using testing::Mock;
+using testing::Values;
 
 namespace ui {
 
@@ -795,10 +796,12 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowDragControllerTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandWindowDragControllerTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
index 64ee858..dc2dabb0 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
@@ -12,6 +12,8 @@
 
 namespace ui {
 
+using ::testing::Values;
+
 namespace {
 
 constexpr gfx::Rect kDefaultBounds(0, 0, 100, 100);
@@ -230,10 +232,12 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowManagerTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandWindowManagerTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 8f92cce7..3ee1a0c 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -55,6 +55,7 @@
 using ::testing::Return;
 using ::testing::SaveArg;
 using ::testing::StrEq;
+using ::testing::Values;
 
 namespace ui {
 
@@ -139,7 +140,7 @@
                                const gfx::Rect bounds) {
     auto* popup = GetTextXdgPopupByWindow(menu_window);
     ASSERT_TRUE(popup);
-    if (GetParam() == kXdgShellV6) {
+    if (GetParam().shell_version == wl::ShellVersion::kV6) {
       zxdg_popup_v6_send_configure(popup->resource(), bounds.x(), bounds.y(),
                                    bounds.width(), bounds.height());
     } else {
@@ -1973,7 +1974,7 @@
 TEST_P(WaylandWindowTest, AdjustPopupBounds) {
   PopupPosition menu_window_positioner, nested_menu_window_positioner;
 
-  if (GetParam() == kXdgShellV6) {
+  if (GetParam().shell_version == wl::ShellVersion::kV6) {
     menu_window_positioner = {
         gfx::Rect(439, 46, 1, 30), gfx::Size(287, 409),
         ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT,
@@ -2186,7 +2187,7 @@
 TEST_P(WaylandWindowTest, OnCloseRequest) {
   EXPECT_CALL(delegate_, OnCloseRequest());
 
-  if (GetParam() == kXdgShellV6)
+  if (GetParam().shell_version == wl::ShellVersion::kV6)
     zxdg_toplevel_v6_send_close(xdg_surface_->xdg_toplevel()->resource());
   else
     xdg_toplevel_send_close(xdg_surface_->xdg_toplevel()->resource());
@@ -2648,16 +2649,16 @@
 
   // We can't mock all those methods above as long as the xdg_toplevel is
   // created and destroyed on each show and hide call. However, it is the same
-  // WaylandToplevelWindow object that cached the values we set and must restore
-  // them on Show().
+  // WaylandToplevelWindow object that cached the values we set and must
+  // restore them on Show().
   EXPECT_EQ(mock_xdg_toplevel->min_size(), min_size.value());
   EXPECT_EQ(mock_xdg_toplevel->max_size(), max_size.value());
   EXPECT_EQ(std::string(kAppId), mock_xdg_toplevel->app_id());
   EXPECT_EQ(mock_xdg_toplevel->title(), base::UTF16ToUTF8(kTitle));
 }
 
-// Tests that a popup window is created using the serial of button press events
-// as required by the Wayland protocol spec.
+// Tests that a popup window is created using the serial of button press
+// events as required by the Wayland protocol spec.
 TEST_P(WaylandWindowTest, CreatesPopupOnButtonPressSerial) {
   wl_seat_send_capabilities(
       server_.seat()->resource(),
@@ -2969,9 +2970,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandWindowTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
index 6c45841..ecf6c8a 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
@@ -17,7 +17,6 @@
 
 namespace {
 
-constexpr uint32_t kXdgVersionStable = 7;
 constexpr uint32_t kZAuraShellVersion = 14;
 
 }  // namespace
@@ -26,7 +25,7 @@
   base::test::SingleThreadTaskEnvironment task_environment(
       base::test::SingleThreadTaskEnvironment::MainThreadType::UI);
   wl::TestWaylandServerThread server;
-  ASSERT_TRUE(server.Start(kXdgVersionStable));
+  ASSERT_TRUE(server.Start({.shell_version = wl::ShellVersion::kStable}));
   wl::GlobalObject zaura_shell_obj(
       &zaura_shell_interface, nullptr /* implementation */, kZAuraShellVersion);
   zaura_shell_obj.Initialize(server.display());
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
index 7cf3193..967553c 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
@@ -44,7 +44,7 @@
   Stop();
 }
 
-bool TestWaylandServerThread::Start(uint32_t shell_version) {
+bool TestWaylandServerThread::Start(const ServerConfig& config) {
   display_.reset(wl_display_create());
   if (!display_)
     return false;
@@ -72,14 +72,12 @@
     return false;
   if (!seat_.Initialize(display_.get()))
     return false;
-  if (shell_version == 6) {
+  if (config.shell_version == ShellVersion::kV6) {
     if (!zxdg_shell_v6_.Initialize(display_.get()))
       return false;
-  } else if (shell_version == 7) {
+  } else {
     if (!xdg_shell_.Initialize(display_.get()))
       return false;
-  } else {
-    NOTREACHED() << "Unsupported shell version: " << shell_version;
   }
   if (!zwp_text_input_manager_v1_.Initialize(display_.get()))
     return false;
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index 712ce602..1faeee2 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -7,6 +7,7 @@
 
 #include <wayland-server-core.h>
 
+#include <cstdint>
 #include <memory>
 #include <vector>
 
@@ -36,6 +37,16 @@
   void operator()(wl_display* display);
 };
 
+// Server configuration related enums and structs.
+enum class ShellVersion { kV6, kStable };
+enum class PrimarySelectionProtocol { kNone, kGtk, kZwp };
+
+struct ServerConfig {
+  ShellVersion shell_version = ShellVersion::kStable;
+  PrimarySelectionProtocol primary_selection_protocol =
+      PrimarySelectionProtocol::kNone;
+};
+
 class TestWaylandServerThread : public base::Thread,
                                 base::MessagePumpLibevent::FdWatcher {
  public:
@@ -51,7 +62,7 @@
   // wl_display_connect).
   // Instantiates an xdg_shell of version |shell_version|; versions 6 and 7
   // (stable) are supported.
-  bool Start(uint32_t shell_version);
+  bool Start(const ServerConfig& config);
 
   // Pauses the server thread when it becomes idle.
   void Pause();
diff --git a/ui/ozone/platform/wayland/test/wayland_test.cc b/ui/ozone/platform/wayland/test/wayland_test.cc
index bc378f0d..f18836a 100644
--- a/ui/ozone/platform/wayland/test/wayland_test.cc
+++ b/ui/ozone/platform/wayland/test/wayland_test.cc
@@ -133,7 +133,7 @@
   // surfaces send other data like states, heights and widths.
   // Please note that toplevel surfaces may not exist if the surface was created
   // for the popup role.
-  if (GetParam() == kXdgShellV6) {
+  if (GetParam().shell_version == wl::ShellVersion::kV6) {
     if (xdg_surface->xdg_toplevel()) {
       zxdg_toplevel_v6_send_configure(xdg_surface->xdg_toplevel()->resource(),
                                       width, height, states);
diff --git a/ui/ozone/platform/wayland/test/wayland_test.h b/ui/ozone/platform/wayland/test/wayland_test.h
index f08e25a..cc34586 100644
--- a/ui/ozone/platform/wayland/test/wayland_test.h
+++ b/ui/ozone/platform/wayland/test/wayland_test.h
@@ -33,12 +33,9 @@
 class ScopedKeyboardLayoutEngine;
 class WaylandScreen;
 
-const uint32_t kXdgShellV6 = 6;
-const uint32_t kXdgShellStable = 7;
-
 // WaylandTest is a base class that sets up a display, window, and test server,
 // and allows easy synchronization between them.
-class WaylandTest : public ::testing::TestWithParam<uint32_t> {
+class WaylandTest : public ::testing::TestWithParam<wl::ServerConfig> {
  public:
   WaylandTest();
   ~WaylandTest() override;
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
index 8db2b3a9..e15f070 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
@@ -23,10 +23,11 @@
 #include "ui/ozone/platform/wayland/test/test_zwp_linux_buffer_params.h"
 #include "ui/ozone/platform/wayland/test/wayland_test.h"
 
-using testing::_;
-
 namespace ui {
 
+using testing::_;
+using testing::Values;
+
 namespace {
 
 using MockTerminateGpuCallback =
@@ -1671,9 +1672,11 @@
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandBufferManagerTest,
-                         ::testing::Values(kXdgShellStable));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
                          WaylandBufferManagerTest,
-                         ::testing::Values(kXdgShellV6));
+                         Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
 
 }  // namespace ui
diff --git a/ui/views/animation/ink_drop.cc b/ui/views/animation/ink_drop.cc
index 2b7ecd8c..823198e 100644
--- a/ui/views/animation/ink_drop.cc
+++ b/ui/views/animation/ink_drop.cc
@@ -106,24 +106,9 @@
     observer.InkDropRippleAnimationEnded(ink_drop_state);
 }
 
-InkDropContainerView::InkDropContainerView() = default;
-
-void InkDropContainerView::AddInkDropLayer(ui::Layer* ink_drop_layer) {
-  SetPaintToLayer();
-  SetVisible(true);
-  layer()->SetFillsBoundsOpaquely(false);
-  layer()->Add(ink_drop_layer);
-}
-
-void InkDropContainerView::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
-  layer()->Remove(ink_drop_layer);
-  SetVisible(false);
-  DestroyLayer();
-}
-
-bool InkDropContainerView::GetCanProcessEventsWithinSubtree() const {
+InkDropContainerView::InkDropContainerView() {
   // Ensure the container View is found as the EventTarget instead of this.
-  return false;
+  SetCanProcessEventsWithinSubtree(false);
 }
 
 BEGIN_METADATA(InkDropContainerView, views::View)
diff --git a/ui/views/animation/ink_drop.h b/ui/views/animation/ink_drop.h
index 48aa47f..91e6e4b5 100644
--- a/ui/views/animation/ink_drop.h
+++ b/ui/views/animation/ink_drop.h
@@ -130,18 +130,14 @@
 // as a non-ancestor view to labels so that the labels can paint on an opaque
 // canvas. This is used to avoid ugly text renderings when labels with subpixel
 // rendering enabled are painted onto a non-opaque canvas.
-class VIEWS_EXPORT InkDropContainerView : public views::View {
+// TODO(pbos): Replace with a function that returns unique_ptr<View>, this only
+// calls SetProcessEventsWithinSubtree(false) right now.
+class VIEWS_EXPORT InkDropContainerView : public View {
  public:
   METADATA_HEADER(InkDropContainerView);
   InkDropContainerView();
   InkDropContainerView(const InkDropContainerView&) = delete;
   InkDropContainerView& operator=(const InkDropContainerView&) = delete;
-
-  void AddInkDropLayer(ui::Layer* ink_drop_layer);
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer);
-
-  // View:
-  bool GetCanProcessEventsWithinSubtree() const override;
 };
 
 }  // namespace views
diff --git a/ui/views/animation/ink_drop_host_view.cc b/ui/views/animation/ink_drop_host_view.cc
index 11290b9..12a5929b 100644
--- a/ui/views/animation/ink_drop_host_view.cc
+++ b/ui/views/animation/ink_drop_host_view.cc
@@ -55,27 +55,24 @@
   destroying_ = true;
 }
 
-void InkDropHostView::AddInkDropLayer(ui::Layer* ink_drop_layer) {
-  // If a clip is provided, use that as it is more performant than a mask.
-  if (!AddInkDropClip(ink_drop_layer))
-    InstallInkDropMask(ink_drop_layer);
-  AddLayerBeneathView(ink_drop_layer);
+void InkDropHostView::SetAddInkDropLayerCallback(
+    base::RepeatingCallback<void(ui::Layer*)> callback) {
+  add_ink_drop_layer_callback_ = std::move(callback);
 }
 
-void InkDropHostView::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
-  // No need to do anything when called during shutdown, and if a derived
-  // class has overridden Add/RemoveInkDropLayer, running this implementation
-  // would be wrong.
-  if (destroying_)
-    return;
-  RemoveLayerBeneathView(ink_drop_layer);
+const base::RepeatingCallback<void(ui::Layer*)>&
+InkDropHostView::GetAddInkDropLayerCallback() const {
+  return add_ink_drop_layer_callback_;
+}
 
-  // Remove clipping.
-  ink_drop_layer->SetClipRect(gfx::Rect());
-  ink_drop_layer->SetRoundedCornerRadius(gfx::RoundedCornersF(0.f));
+void InkDropHostView::SetRemoveInkDropLayerCallback(
+    base::RepeatingCallback<void(ui::Layer*)> callback) {
+  remove_ink_drop_layer_callback_ = std::move(callback);
+}
 
-  // Layers safely handle destroying a mask layer before the masked layer.
-  ink_drop_mask_.reset();
+const base::RepeatingCallback<void(ui::Layer*)>&
+InkDropHostView::GetRemoveInkDropLayerCallback() const {
+  return remove_ink_drop_layer_callback_;
 }
 
 std::unique_ptr<InkDrop> InkDropHostView::CreateInkDrop() {
@@ -262,6 +259,40 @@
   OnPropertyChanged(&ink_drop_, kPropertyEffectsNone);
 }
 
+void InkDropHostView::AddInkDropLayer(ui::Layer* ink_drop_layer) {
+  if (add_ink_drop_layer_callback_) {
+    add_ink_drop_layer_callback_.Run(ink_drop_layer);
+    return;
+  }
+
+  // If a clip is provided, use that as it is more performant than a mask.
+  if (!AddInkDropClip(ink_drop_layer))
+    InstallInkDropMask(ink_drop_layer);
+  AddLayerBeneathView(ink_drop_layer);
+}
+
+void InkDropHostView::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
+  // No need to do anything when called during shutdown, and if a derived
+  // class has set `remove_ink_drop_layer_callback_` then running that callback
+  // is very likely to be a use-after-free.
+  if (destroying_)
+    return;
+
+  if (remove_ink_drop_layer_callback_) {
+    remove_ink_drop_layer_callback_.Run(ink_drop_layer);
+    return;
+  }
+
+  RemoveLayerBeneathView(ink_drop_layer);
+
+  // Remove clipping.
+  ink_drop_layer->SetClipRect(gfx::Rect());
+  ink_drop_layer->SetRoundedCornerRadius(gfx::RoundedCornersF(0.f));
+
+  // Layers safely handle destroying a mask layer before the masked layer.
+  ink_drop_mask_.reset();
+}
+
 std::unique_ptr<InkDropRipple> InkDropHostView::CreateInkDropForSquareRipple(
     const gfx::Point& center_point,
     const gfx::Size& size) const {
@@ -276,41 +307,6 @@
   return !!ink_drop_;
 }
 
-void InkDropHostView::InstallInkDropMask(ui::Layer* ink_drop_layer) {
-  ink_drop_mask_ = CreateInkDropMask();
-  if (ink_drop_mask_)
-    ink_drop_layer->SetMaskLayer(ink_drop_mask_->layer());
-}
-
-void InkDropHostView::ResetInkDropMask() {
-  ink_drop_mask_.reset();
-}
-
-bool InkDropHostView::AddInkDropClip(ui::Layer* ink_drop_layer) {
-  base::Optional<gfx::RRectF> clipping_data =
-      HighlightPathGenerator::GetRoundRectForView(this);
-  if (!clipping_data)
-    return false;
-
-  ink_drop_layer->SetClipRect(gfx::ToEnclosingRect(clipping_data->rect()));
-  auto get_corner_radii =
-      [&clipping_data](gfx::RRectF::Corner corner) -> float {
-    return clipping_data.value().GetCornerRadii(corner).x();
-  };
-  gfx::RoundedCornersF rounded_corners;
-  rounded_corners.set_upper_left(
-      get_corner_radii(gfx::RRectF::Corner::kUpperLeft));
-  rounded_corners.set_upper_right(
-      get_corner_radii(gfx::RRectF::Corner::kUpperRight));
-  rounded_corners.set_lower_right(
-      get_corner_radii(gfx::RRectF::Corner::kLowerRight));
-  rounded_corners.set_lower_left(
-      get_corner_radii(gfx::RRectF::Corner::kLowerLeft));
-  ink_drop_layer->SetRoundedCornerRadius(rounded_corners);
-  ink_drop_layer->SetIsFastRoundedCorner(true);
-  return true;
-}
-
 // static
 gfx::Size InkDropHostView::CalculateLargeInkDropSize(
     const gfx::Size& small_size) {
@@ -340,7 +336,42 @@
       const_cast<const InkDropHostView*>(this)->GetEventHandler());
 }
 
+bool InkDropHostView::AddInkDropClip(ui::Layer* ink_drop_layer) {
+  base::Optional<gfx::RRectF> clipping_data =
+      HighlightPathGenerator::GetRoundRectForView(this);
+  if (!clipping_data)
+    return false;
+
+  ink_drop_layer->SetClipRect(gfx::ToEnclosingRect(clipping_data->rect()));
+  auto get_corner_radii =
+      [&clipping_data](gfx::RRectF::Corner corner) -> float {
+    return clipping_data.value().GetCornerRadii(corner).x();
+  };
+  gfx::RoundedCornersF rounded_corners;
+  rounded_corners.set_upper_left(
+      get_corner_radii(gfx::RRectF::Corner::kUpperLeft));
+  rounded_corners.set_upper_right(
+      get_corner_radii(gfx::RRectF::Corner::kUpperRight));
+  rounded_corners.set_lower_right(
+      get_corner_radii(gfx::RRectF::Corner::kLowerRight));
+  rounded_corners.set_lower_left(
+      get_corner_radii(gfx::RRectF::Corner::kLowerLeft));
+  ink_drop_layer->SetRoundedCornerRadius(rounded_corners);
+  ink_drop_layer->SetIsFastRoundedCorner(true);
+  return true;
+}
+
+void InkDropHostView::InstallInkDropMask(ui::Layer* ink_drop_layer) {
+  ink_drop_mask_ = CreateInkDropMask();
+  DCHECK(ink_drop_mask_);
+  ink_drop_layer->SetMaskLayer(ink_drop_mask_->layer());
+}
+
 BEGIN_METADATA(InkDropHostView, View)
+ADD_PROPERTY_METADATA(base::RepeatingCallback<void(ui::Layer*)>,
+                      AddInkDropLayerCallback)
+ADD_PROPERTY_METADATA(base::RepeatingCallback<void(ui::Layer*)>,
+                      RemoveInkDropLayerCallback)
 ADD_PROPERTY_METADATA(base::RepeatingCallback<std::unique_ptr<InkDrop>()>,
                       CreateInkDropCallback)
 ADD_PROPERTY_METADATA(base::RepeatingCallback<std::unique_ptr<InkDropRipple>()>,
diff --git a/ui/views/animation/ink_drop_host_view.h b/ui/views/animation/ink_drop_host_view.h
index 99a57abe..bcfa225 100644
--- a/ui/views/animation/ink_drop_host_view.h
+++ b/ui/views/animation/ink_drop_host_view.h
@@ -52,11 +52,34 @@
   InkDropHostView& operator=(const InkDropHostView&) = delete;
   ~InkDropHostView() override;
 
-  // Adds the |ink_drop_layer| in to a visible layer tree.
-  virtual void AddInkDropLayer(ui::Layer* ink_drop_layer);
+  // TODO(pbos): Re-think this API, we may want to expose adding a Layer beneath
+  // a child view to add the effect in the middle of the layer stack. See
+  // ToggleButton.
+  //
+  // Adds a callback for attaching |ink_drop_layer| in to a visible layer tree.
+  //
+  // Do not call from new code. Most uses for this API should be overriding
+  // View::AddLayerBeneathView instead. New ones should re-think the API.
+  void SetAddInkDropLayerCallback(
+      base::RepeatingCallback<void(ui::Layer*)> callback);
 
-  // Removes |ink_drop_layer| from the layer tree.
-  virtual void RemoveInkDropLayer(ui::Layer* ink_drop_layer);
+  // Only here to support metadata.
+  const base::RepeatingCallback<void(ui::Layer*)>& GetAddInkDropLayerCallback()
+      const;
+
+  // TODO(pbos): Re-think this API, we may want to expose adding a Layer beneath
+  // a child view to add the effect in the middle of the layer stack. See
+  // ToggleButton.
+  //
+  // Adds a callback for removing |ink_drop_layer| from the layer tree.
+  //
+  // Do not call from new code. Most uses for this API should be overriding
+  // View::AddLayerBeneathView instead. New ones should re-think the API.
+  void SetRemoveInkDropLayerCallback(
+      base::RepeatingCallback<void(ui::Layer*)> callback);
+  // Only here to support metadata.
+  const base::RepeatingCallback<void(ui::Layer*)>&
+  GetRemoveInkDropLayerCallback() const;
 
   // Returns a configured InkDrop. To override default behavior call
   // SetCreateInkDropRippleCallback().
@@ -93,7 +116,7 @@
 
   // Creates and returns the visual effect used for hover and focus. Used by
   // InkDropImpl instances.
-  virtual std::unique_ptr<InkDropHighlight> CreateInkDropHighlight() const;
+  std::unique_ptr<InkDropHighlight> CreateInkDropHighlight() const;
 
   // Callback version of CreateInkDropHighlight(). Note that this is called in
   // the base implementation of CreateInkDropHighlight(), so if "it's not
@@ -105,15 +128,9 @@
   const base::RepeatingCallback<std::unique_ptr<InkDropHighlight>()>&
   GetCreateInkDropHighlightCallback() const;
 
-  // Subclasses can override to return a mask for the ink drop. By default,
-  // this generates a mask based on HighlightPathGenerator.
-  // TODO(pbos): Replace overrides with HighlightPathGenerator usage and remove
-  // this function.
-  virtual std::unique_ptr<views::InkDropMask> CreateInkDropMask() const;
-
-  // Callback version of CreateInkDropMask(). Note that this is called in the
-  // base implementation of CreateInkDropMask(), so if "it's not working", check
-  // the class hierarchy for overrides.
+  // Callback replacement of CreateInkDropMask().
+  // TODO(pbos): Investigate removing this. It currently is only used by
+  // ToolbarButton.
   void SetCreateInkDropMaskCallback(
       base::RepeatingCallback<std::unique_ptr<InkDropMask>()> callback);
 
@@ -192,6 +209,11 @@
   // changes, to trigger the corresponding property change notification here.
   void OnInkDropHighlightedChanged();
 
+  // Methods called by InkDrop for attaching its layer.
+  // TODO(pbos): Investigate using direct calls on View::AddLayerBeneathView.
+  void AddInkDropLayer(ui::Layer* ink_drop_layer);
+  void RemoveInkDropLayer(ui::Layer* ink_drop_layer);
+
  protected:
   // Size used by default for the SquareInkDropRipple.
   static constexpr gfx::Size kDefaultInkDropSize = gfx::Size(24, 24);
@@ -204,18 +226,6 @@
   // Returns true if an ink drop instance has been created.
   bool HasInkDrop() const;
 
-  // Initializes and sets a mask on |ink_drop_layer|. No-op if
-  // CreateInkDropMask() returns null. This will not run if |AddInkDropClip()|
-  // succeeds in the default implementation of |AddInkDropLayer()|.
-  void InstallInkDropMask(ui::Layer* ink_drop_layer);
-
-  void ResetInkDropMask();
-
-  // Adds a clip rect on the root layer of the ink drop impl. This is a more
-  // performant alternative to using circles or rectangle mask layers. Returns
-  // true if a clip was added.
-  bool AddInkDropClip(ui::Layer* ink_drop_layer);
-
   // Returns a large ink drop size based on the |small_size| that works well
   // with the SquareInkDropRipple animation durations.
   static gfx::Size CalculateLargeInkDropSize(const gfx::Size& small_size);
@@ -246,6 +256,19 @@
   const InkDropEventHandler* GetEventHandler() const;
   InkDropEventHandler* GetEventHandler();
 
+  // This generates a mask for the InkDrop.
+  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const;
+
+  // Adds a clip rect on the root layer of the ink drop impl. This is a more
+  // performant alternative to using circles or rectangle mask layers. Returns
+  // true if a clip was added.
+  bool AddInkDropClip(ui::Layer* ink_drop_layer);
+
+  // Initializes and sets a mask on `ink_drop_layer`. This will not run if
+  // AddInkDropClip() succeeds in the default implementation of
+  // AddInkDropLayer().
+  void InstallInkDropMask(ui::Layer* ink_drop_layer);
+
   // Defines what type of |ink_drop_| to create.
   InkDropMode ink_drop_mode_ = InkDropMode::OFF;
 
@@ -273,6 +296,8 @@
 
   std::unique_ptr<views::InkDropMask> ink_drop_mask_;
 
+  base::RepeatingCallback<void(ui::Layer*)> add_ink_drop_layer_callback_;
+  base::RepeatingCallback<void(ui::Layer*)> remove_ink_drop_layer_callback_;
   base::RepeatingCallback<std::unique_ptr<InkDrop>()> create_ink_drop_callback_;
   base::RepeatingCallback<std::unique_ptr<InkDropRipple>()>
       create_ink_drop_ripple_callback_;
diff --git a/ui/views/animation/test/test_ink_drop_host.cc b/ui/views/animation/test/test_ink_drop_host.cc
index 986d351..ae61fa73 100644
--- a/ui/views/animation/test/test_ink_drop_host.cc
+++ b/ui/views/animation/test/test_ink_drop_host.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/animation/ink_drop_impl.h"
@@ -78,18 +79,32 @@
 
 TestInkDropHost::TestInkDropHost() {
   InkDrop::UseInkDropWithoutAutoHighlight(this);
+
+  SetAddInkDropLayerCallback(base::BindRepeating(
+      [](TestInkDropHost* host, ui::Layer*) {
+        ++host->num_ink_drop_layers_added_;
+      },
+      this));
+  SetRemoveInkDropLayerCallback(base::BindRepeating(
+      [](TestInkDropHost* host, ui::Layer*) {
+        ++host->num_ink_drop_layers_removed_;
+      },
+      this));
+  SetCreateInkDropHighlightCallback(base::BindRepeating(
+      [](TestInkDropHost* host) -> std::unique_ptr<views::InkDropHighlight> {
+        auto highlight = std::make_unique<TestInkDropHighlight>(
+            host->size(), 0, gfx::PointF(), SK_ColorBLACK);
+        if (host->disable_timers_for_test_)
+          highlight->GetTestApi()->SetDisableAnimationTimers(true);
+        host->num_ink_drop_highlights_created_++;
+        host->last_ink_drop_highlight_ = highlight.get();
+        return highlight;
+      },
+      this));
 }
 
 TestInkDropHost::~TestInkDropHost() = default;
 
-void TestInkDropHost::AddInkDropLayer(ui::Layer* ink_drop_layer) {
-  ++num_ink_drop_layers_added_;
-}
-
-void TestInkDropHost::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
-  ++num_ink_drop_layers_removed_;
-}
-
 std::unique_ptr<InkDropRipple> TestInkDropHost::CreateInkDropRipple() const {
   std::unique_ptr<InkDropRipple> ripple(new TestInkDropRipple(
       size(), 0, size(), 0, gfx::Point(), SK_ColorBLACK, 0.175f));
@@ -100,15 +115,4 @@
   return ripple;
 }
 
-std::unique_ptr<InkDropHighlight> TestInkDropHost::CreateInkDropHighlight()
-    const {
-  auto highlight = std::make_unique<TestInkDropHighlight>(
-      size(), 0, gfx::PointF(), SK_ColorBLACK);
-  if (disable_timers_for_test_)
-    highlight->GetTestApi()->SetDisableAnimationTimers(true);
-  num_ink_drop_highlights_created_++;
-  last_ink_drop_highlight_ = highlight.get();
-  return highlight;
-}
-
 }  // namespace views
diff --git a/ui/views/animation/test/test_ink_drop_host.h b/ui/views/animation/test/test_ink_drop_host.h
index 099e92d89..93636fb 100644
--- a/ui/views/animation/test/test_ink_drop_host.h
+++ b/ui/views/animation/test/test_ink_drop_host.h
@@ -43,10 +43,7 @@
   }
 
   // InkDropHostView:
-  void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
   std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
-  std::unique_ptr<InkDropHighlight> CreateInkDropHighlight() const override;
 
  private:
   int num_ink_drop_layers_added_ = 0;
diff --git a/ui/views/controls/button/button_unittest.cc b/ui/views/controls/button/button_unittest.cc
index dabaea3..bb2cfcc 100644
--- a/ui/views/controls/button/button_unittest.cc
+++ b/ui/views/controls/button/button_unittest.cc
@@ -85,24 +85,11 @@
     return custom_key_click_action_;
   }
 
-  void OnClickCanceled(const ui::Event& event) override { canceled_ = true; }
-
   // Button:
-  void AddInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ++ink_drop_layer_add_count_;
-    Button::AddInkDropLayer(ink_drop_layer);
-  }
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ++ink_drop_layer_remove_count_;
-    Button::RemoveInkDropLayer(ink_drop_layer);
-  }
+  void OnClickCanceled(const ui::Event& event) override { canceled_ = true; }
 
   bool pressed() const { return pressed_; }
   bool canceled() const { return canceled_; }
-  int ink_drop_layer_add_count() const { return ink_drop_layer_add_count_; }
-  int ink_drop_layer_remove_count() const {
-    return ink_drop_layer_remove_count_;
-  }
 
   void set_custom_key_click_action(KeyClickAction custom_key_click_action) {
     custom_key_click_action_ = custom_key_click_action;
@@ -114,15 +101,12 @@
   }
 
   // Raised visibility of OnFocus() to public
-  void OnFocus() override { Button::OnFocus(); }
+  using Button::OnFocus;
 
  private:
   bool pressed_ = false;
   bool canceled_ = false;
 
-  int ink_drop_layer_add_count_ = 0;
-  int ink_drop_layer_remove_count_ = 0;
-
   KeyClickAction custom_key_click_action_ = KeyClickAction::kNone;
 
   DISALLOW_COPY_AND_ASSIGN(TestButton);
@@ -751,16 +735,10 @@
   ~VisibilityTestButton() override {
     if (layer())
       ADD_FAILURE();
-  }
-
-  // TestButton:
-  void AddInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ADD_FAILURE();
-    TestButton::AddInkDropLayer(ink_drop_layer);
-  }
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ADD_FAILURE();
-    TestButton::RemoveInkDropLayer(ink_drop_layer);
+    auto ink_drop_layer_add_failure_callback =
+        base::BindRepeating([](ui::Layer*) { ADD_FAILURE(); });
+    SetAddInkDropLayerCallback(ink_drop_layer_add_failure_callback);
+    SetRemoveInkDropLayerCallback(ink_drop_layer_add_failure_callback);
   }
 };
 
diff --git a/ui/views/controls/button/toggle_button.cc b/ui/views/controls/button/toggle_button.cc
index 5bda00e..afc4d205 100644
--- a/ui/views/controls/button/toggle_button.cc
+++ b/ui/views/controls/button/toggle_button.cc
@@ -8,6 +8,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/bind.h"
 #include "cc/paint/paint_flags.h"
 #include "third_party/skia/include/core/SkDrawLooper.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -136,6 +137,10 @@
   SetHasInkDropActionOnClick(true);
   views::InkDrop::UseInkDropForSquareRipple(this,
                                             /*highlight_on_hover=*/false);
+  SetAddInkDropLayerCallback(base::BindRepeating(
+      &InkDropHostView::AddInkDropLayer, base::Unretained(thumb_view_)));
+  SetRemoveInkDropLayerCallback(base::BindRepeating(
+      &InkDropHostView::RemoveInkDropLayer, base::Unretained(thumb_view_)));
 }
 
 ToggleButton::~ToggleButton() {
@@ -317,14 +322,6 @@
   canvas->Restore();
 }
 
-void ToggleButton::AddInkDropLayer(ui::Layer* ink_drop_layer) {
-  thumb_view_->AddInkDropLayer(ink_drop_layer);
-}
-
-void ToggleButton::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
-  thumb_view_->RemoveInkDropLayer(ink_drop_layer);
-}
-
 std::unique_ptr<InkDropRipple> ToggleButton::CreateInkDropRipple() const {
   gfx::Rect rect = thumb_view_->GetLocalBounds();
   rect.Inset(-ThumbView::GetShadowOutsets());
diff --git a/ui/views/controls/button/toggle_button.h b/ui/views/controls/button/toggle_button.h
index 82bcb7c..b9c2e702 100644
--- a/ui/views/controls/button/toggle_button.h
+++ b/ui/views/controls/button/toggle_button.h
@@ -69,8 +69,6 @@
   // Button:
   void NotifyClick(const ui::Event& event) override;
   void PaintButtonContents(gfx::Canvas* canvas) override;
-  void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
   std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
   SkColor GetInkDropBaseColor() const override;
 
diff --git a/ui/views/controls/button/toggle_button_unittest.cc b/ui/views/controls/button/toggle_button_unittest.cc
index 4daacc1..a463d2ca 100644
--- a/ui/views/controls/button/toggle_button_unittest.cc
+++ b/ui/views/controls/button/toggle_button_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind.h"
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -19,7 +20,33 @@
 
 class TestToggleButton : public ToggleButton {
  public:
-  explicit TestToggleButton(int* counter) : counter_(counter) {}
+  explicit TestToggleButton(int* counter) {
+    // TODO(pbos): Find a better testing strategy for this or throw out tests
+    // that rely on monitoring AddInkDropLayerCallbacks (which should hopefully
+    // go away). This is massively gross, but mimics virtual override of
+    // ToggleButton's inkdrop behavior in order to monitor it.
+    base::RepeatingCallback<void(ui::Layer*)> base_add_callback =
+        GetAddInkDropLayerCallback();
+    base::RepeatingCallback<void(ui::Layer*)> base_remove_callback =
+        GetRemoveInkDropLayerCallback();
+    SetAddInkDropLayerCallback(base::BindRepeating(
+        [](int* counter,
+           base::RepeatingCallback<void(ui::Layer*)> base_callback,
+           ui::Layer* layer) {
+          ++(*counter);
+          base_callback.Run(layer);
+        },
+        counter, base_add_callback));
+    SetRemoveInkDropLayerCallback(base::BindRepeating(
+        [](int* counter,
+           base::RepeatingCallback<void(ui::Layer*)> base_callback,
+           ui::Layer* layer) {
+          --(*counter);
+          base_callback.Run(layer);
+        },
+        counter, base_remove_callback));
+  }
+
   ~TestToggleButton() override {
     // Calling SetInkDropMode() in this subclass allows this class's
     // implementation of RemoveInkDropLayer() to be called. The same
@@ -29,21 +56,6 @@
 
   using View::Focus;
 
- protected:
-  // ToggleButton:
-  void AddInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ++(*counter_);
-    ToggleButton::AddInkDropLayer(ink_drop_layer);
-  }
-
-  void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override {
-    ToggleButton::RemoveInkDropLayer(ink_drop_layer);
-    --(*counter_);
-  }
-
- private:
-  int* counter_;
-
   DISALLOW_COPY_AND_ASSIGN(TestToggleButton);
 };
 
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/activation_code_page.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/activation_code_page.html
index 60bde27..e4801a8 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/activation_code_page.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/activation_code_page.html
@@ -177,7 +177,7 @@
     </style>
     <base-page>
       <div slot="page-body" id="pageBody" class="animate">
-        <span id="scanQrCodeDescription">
+        <span aria-live="polite">
           [[getDescription_(showNoProfilesMessage, cameraCount_ )]]
         </span>
         <template is="dom-if"
@@ -198,7 +198,6 @@
               <cr-button class="label"
                   id="startScanningButton"
                   on-click="startScanning_"
-                  aria-describedby="scanQrCodeDescription"
                   disabled="[[isUiElementDisabled_(UiElement.START_SCANNING, state_, showBusy)]]">
                 <iron-icon class="button-image" icon="cellular-setup:camera"></iron-icon>
                 [[i18n('useCamera')]]
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html
index c814032..1d10339a 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html
@@ -54,7 +54,9 @@
     </style>
     <base-page>
       <div slot="page-body">
-        <div id="description">[[i18n('confirmationCodeMessage')]]</div>
+        <div aria-live="polite">
+          [[i18n('confirmationCodeMessage')]]
+        </div>
         <div id="outerDiv" class="layout horizontal center">
           <div class="container">
             <div id="details" hidden$="[[!shouldShowProfileDetails_(profile)]]">
@@ -68,7 +70,6 @@
               <cr-input id="confirmationCode"
                   label="[[i18n('confirmationCodeInput')]]"
                   value="{{confirmationCode}}"
-                  aria-describedby="description"
                   error-message="[[i18n('confirmationCodeError')]]"
                   invalid="[[showError]]"
                   disabled="[[showBusy]]"