diff --git a/DEPS b/DEPS
index 8a94133..7ee4eff 100644
--- a/DEPS
+++ b/DEPS
@@ -213,7 +213,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:7528d700b89db0b6feb32be098d40597f738d3c7',
+  'luci_go': 'git_revision:9c88febc5ab63469baf5aae05a74f8e419970ef3',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -245,19 +245,19 @@
   # 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': 'a2672102702d7b11cbd70c465fceb3fb9e58f88f',
+  'skia_revision': '83859e27c361fb5ceb17e6f71716f119d637c9ca',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'fe6e3df471e24517f098e874d505b1728c332915',
+  'v8_revision': '9e79bc53ba16930f18d7fbef2bbdb0b807547888',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '602b957b88365a9cd34ab73efca4c96675bbb149',
+  'angle_revision': '706e44b06a46659c933603c2c7e99d603717bc5b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'bca23447ad4667a7b79973569ab5d8d905d211ac',
+  'swiftshader_revision': '78491358c250fc627cb02b8d66679dc4a18dc12c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -320,7 +320,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': 'f5cdbdfb498c834043f5426a2887af970ad4b188',
+  'devtools_frontend_revision': '7adcfa6de39c286e829362323ae33295585cbf1d',
   # 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.
@@ -663,7 +663,7 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'd3e8789ea703ef617c51a36afe8c5dd1348b1a74',
+    'url': Var('chromium_git') + '/website.git' + '@' + '70ff5876887c72323938990ba71fa15011e31433',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1085,12 +1085,12 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'd35c8af9e3c5814d18ad0a820511b28f53cc69e6',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'e142875d49189d6d52a2e012b7d4a2a653ebbd69',
       'condition': 'checkout_linux',
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd5862557d6a2ceef3f85092aa76682a4cdf2fc4e',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3bf9766110f60f8d328d6caac4792b594d2b7189',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1473,7 +1473,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '448ab50107d2f6382e413564e51e8130e92c4e09',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '33928bc89438655f0ab59f4fb8c56ae4a6aa2afa',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1551,7 +1551,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/android/aemu/release/linux-amd64',
-              'version': 'Ea-MmVNtNTqZyinCufB9t1a9bhgxSk4eLHIfYMTuWnkC'
+              'version': 'myq62o-lUjtKNg97vsknMMge-RUmPfb2nJciJoONW-UC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1570,7 +1570,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '611baecbcedc9cec1f46e38616b6d8880b676c03',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '0d39decb2f0adcbcb943fd31943fcd20398ebe4e',
 
   'src/third_party/r8': {
       'packages': [
@@ -1655,7 +1655,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '62d7d0c928c9a040dce96aa2f16c00e7e67d59cb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@a19b5047ffcafc95fcba3f39e53f229acca6debc',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@c8fe6cd6739fbb957e6768a978336eeb91674c2b',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1764,7 +1764,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@13bf51fd202bc3a8253df69f36b6b517fefa84c2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3c094716117773987bee6aa2129b8ece65b365d0',
     'condition': 'checkout_src_internal',
   },
 
@@ -1783,7 +1783,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'Qa5fQL-FhMVjngdj4mWHgB_HEYJHls_GgerHPU6RBWcC',
+        'version': 'JgpkC-c8buxqr0xk2MuWo-fOWnb2w8hUm7nANUi75MEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index fdc67e7d..2664f04 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1167,7 +1167,7 @@
           for s in ('bling-autoroll-builder', 'v8-ci-autoroll-builder',
                     'wpt-autoroller', 'chrome-weblayer-builder',
                     'lacros-version-skew-roller', 'skylab-test-cros-roller',
-                    'infra-try-recipes-tester')
+                    'infra-try-recipes-tester', 'lacros-tracking-roller')
   ) | set('%s@skia-public.iam.gserviceaccount.com' % s
           for s in ('chromium-autoroll', 'chromium-release-autoroll')
   ) | set('%s@skia-corp.google.com.iam.gserviceaccount.com' % s
diff --git a/android_webview/browser/aw_dark_mode.cc b/android_webview/browser/aw_dark_mode.cc
index 24ca3d25..c39a948a 100644
--- a/android_webview/browser/aw_dark_mode.cc
+++ b/android_webview/browser/aw_dark_mode.cc
@@ -66,7 +66,7 @@
     blink::web_pref::WebPreferences* web_prefs,
     int force_dark_mode,
     int force_dark_behavior,
-    bool allow_algorithmic_darkening) {
+    bool algorithmic_darkening_allowed) {
   if (!sShouldEnableSimplifiedDarkMode) {
     PopulateWebPreferencesForPreT(web_prefs, force_dark_mode,
                                   force_dark_behavior);
@@ -84,8 +84,8 @@
     web_prefs->preferred_color_scheme =
         blink::mojom::PreferredColorScheme::kDark;
   } else if (prefers_dark_from_theme_) {
-    is_dark_mode_ = allow_algorithmic_darkening;
-    web_prefs->force_dark_mode_enabled = allow_algorithmic_darkening;
+    is_dark_mode_ = algorithmic_darkening_allowed;
+    web_prefs->force_dark_mode_enabled = algorithmic_darkening_allowed;
   }
 }
 
diff --git a/android_webview/browser/aw_dark_mode.h b/android_webview/browser/aw_dark_mode.h
index fdd10c2..ebfbe2b8 100644
--- a/android_webview/browser/aw_dark_mode.h
+++ b/android_webview/browser/aw_dark_mode.h
@@ -22,7 +22,7 @@
   void PopulateWebPreferences(blink::web_pref::WebPreferences* web_prefs,
                               int force_dark_mode,
                               int force_dark_behavior,
-                              bool allow_algorithmic_darkening);
+                              bool algorithmic_darkening_allowed);
 
   void DetachFromJavaObject(
       JNIEnv* env,
diff --git a/android_webview/browser/aw_settings.cc b/android_webview/browser/aw_settings.cc
index 3e58fbb..6d8242a 100644
--- a/android_webview/browser/aw_settings.cc
+++ b/android_webview/browser/aw_settings.cc
@@ -509,7 +509,7 @@
     aw_dark_mode->PopulateWebPreferences(
         web_prefs, Java_AwSettings_getForceDarkModeLocked(env, obj),
         Java_AwSettings_getForceDarkBehaviorLocked(env, obj),
-        Java_AwSettings_getAllowAlgorithmicDarkeningLocked(env, obj));
+        Java_AwSettings_isAlgorithmicDarkeningAllowedLocked(env, obj));
   }
 }
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwSettings.java b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
index df69234d..c05880e 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwSettings.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
@@ -69,7 +69,7 @@
     @ForceDarkMode
     private int mForceDarkMode = ForceDarkMode.FORCE_DARK_AUTO;
 
-    private boolean mAllowAlgorithmicDarkening;
+    private boolean mAlgorithmicDarkeningAllowed;
 
     public static final int FORCE_DARK_ONLY = ForceDarkBehavior.FORCE_DARK_ONLY;
     public static final int MEDIA_QUERY_ONLY = ForceDarkBehavior.MEDIA_QUERY_ONLY;
@@ -1694,22 +1694,22 @@
         }
     }
 
-    public boolean getAllowAlgorithmicDarkening() {
+    public boolean isAlgorithmicDarkeningAllowed() {
         synchronized (mAwSettingsLock) {
-            return getAllowAlgorithmicDarkeningLocked();
+            return isAlgorithmicDarkeningAllowedLocked();
         }
     }
 
     @CalledByNative
-    private boolean getAllowAlgorithmicDarkeningLocked() {
+    private boolean isAlgorithmicDarkeningAllowedLocked() {
         assert Thread.holdsLock(mAwSettingsLock);
-        return mAllowAlgorithmicDarkening;
+        return mAlgorithmicDarkeningAllowed;
     }
 
-    public void setAllowAlgorithmicDarkening(boolean allow) {
+    public void setAlgorithmicDarkeningAllowed(boolean allow) {
         synchronized (mAwSettingsLock) {
-            if (mAllowAlgorithmicDarkening != allow) {
-                mAllowAlgorithmicDarkening = allow;
+            if (mAlgorithmicDarkeningAllowed != allow) {
+                mAlgorithmicDarkeningAllowed = allow;
                 mEventHandler.updateWebkitPreferencesLocked();
             }
         }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwDarkModeTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwDarkModeTest.java
index 39ec167d..55471a0 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwDarkModeTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwDarkModeTest.java
@@ -143,7 +143,7 @@
 
         // Check setForceDarkMode has noops, otherwise ForceDarkening will be turned off.
         mAwContents.getSettings().setForceDarkMode(AwSettings.FORCE_DARK_OFF);
-        mAwContents.getSettings().setAllowAlgorithmicDarkening(true);
+        mAwContents.getSettings().setAlgorithmicDarkeningAllowed(true);
         // Set force dark mode again to check no ordering issue.
         mAwContents.getSettings().setForceDarkMode(AwSettings.FORCE_DARK_OFF);
 
@@ -162,7 +162,7 @@
 
         // Check setForceDarkMode has noops, otherwise ForceDarkening will be turned off.
         mAwContents.getSettings().setForceDarkMode(AwSettings.FORCE_DARK_OFF);
-        mAwContents.getSettings().setAllowAlgorithmicDarkening(true);
+        mAwContents.getSettings().setAlgorithmicDarkeningAllowed(true);
         // Set force dark mode again to check no ordering issue.
         mAwContents.getSettings().setForceDarkMode(AwSettings.FORCE_DARK_OFF);
 
diff --git a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
index 62a21f3..1df7f8eb2 100644
--- a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
+++ b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
@@ -210,6 +210,10 @@
 interface HIDConnectionEvent : Event
 interface HID : EventTarget
 
+# Window Controls Overlay API is not implemented on Android.
+interface WindowControlsOverlay : EventTarget
+interface WindowControlsOverlayGeometryChangeEvent : Event
+
 [GLOBAL OBJECT]
     method openDatabase
     attribute eventSender                    # test only
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index dca0909..9fb054f 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -928,6 +928,8 @@
     "style/scoped_light_mode_as_default.cc",
     "style/style_util.cc",
     "style/style_util.h",
+    "style/system_shadow.cc",
+    "style/system_shadow.h",
     "style/system_toast_style.cc",
     "style/system_toast_style.h",
     "system/accessibility/accessibility_feature_disable_dialog.cc",
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 1ad3b91..361ed7432 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -2405,11 +2405,12 @@
 void AccessibilityControllerImpl::UpdateDictationBubble(
     bool visible,
     DictationBubbleIconType icon,
-    const absl::optional<std::u16string>& text) {
+    const absl::optional<std::u16string>& text,
+    const absl::optional<std::vector<std::string>>& hints) {
   DCHECK(dictation().enabled());
   DCHECK(dictation_bubble_controller_);
 
-  dictation_bubble_controller_->UpdateBubble(visible, icon, text);
+  dictation_bubble_controller_->UpdateBubble(visible, icon, text, hints);
 }
 
 }  // namespace ash
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 10f77f2..053d2b0 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -452,7 +452,8 @@
   void UpdateDictationBubble(
       bool visible,
       DictationBubbleIconType icon,
-      const absl::optional<std::u16string>& text) override;
+      const absl::optional<std::u16string>& text,
+      const absl::optional<std::vector<std::string>>& hints) override;
 
   // SessionObserver:
   void OnSigninScreenPrefServiceInitialized(PrefService* prefs) override;
diff --git a/ash/app_list/views/continue_task_view.cc b/ash/app_list/views/continue_task_view.cc
index 1845c2c5..6afdbb3 100644
--- a/ash/app_list/views/continue_task_view.cc
+++ b/ash/app_list/views/continue_task_view.cc
@@ -16,6 +16,7 @@
 #include "ash/public/cpp/style/color_provider.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/highlight_border.h"
 #include "ash/style/style_util.h"
 #include "base/bind.h"
 #include "base/strings/string_util.h"
@@ -67,7 +68,7 @@
 
 ContinueTaskView::ContinueTaskView(AppListViewDelegate* view_delegate,
                                    bool tablet_mode)
-    : view_delegate_(view_delegate) {
+    : view_delegate_(view_delegate), is_tablet_mode_(tablet_mode) {
   SetFocusBehavior(FocusBehavior::ALWAYS);
   SetCallback(base::BindRepeating(&ContinueTaskView::OnButtonPressed,
                                   base::Unretained(this)));
@@ -90,12 +91,8 @@
 
   StyleUtil::ConfigureInkDropAttributes(
       this, StyleUtil::kBaseColor | StyleUtil::kInkDropOpacity);
-  if (tablet_mode) {
-    SetBackground(views::CreateRoundedRectBackground(
-        ColorProvider::Get()->GetBaseLayerColor(
-            ColorProvider::BaseLayerType::kTransparent80),
-        GetCornerRadius(/*tablet_mode=*/true)));
-  }
+
+  UpdateStyleForTabletMode();
 
   views::BoxLayout* layout_manager =
       SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -136,6 +133,7 @@
   views::View::OnThemeChanged();
   bubble_utils::ApplyStyle(title_, bubble_utils::LabelStyle::kBody);
   bubble_utils::ApplyStyle(subtitle_, bubble_utils::LabelStyle::kSubtitle);
+  UpdateStyleForTabletMode();
 }
 
 gfx::Size ContinueTaskView::GetMaximumSize() const {
@@ -297,6 +295,21 @@
   context_menu_runner_->Cancel();
 }
 
+void ContinueTaskView::UpdateStyleForTabletMode() {
+  // Do nothing if the view is not in tablet mode.
+  if (!is_tablet_mode_)
+    return;
+
+  SetBackground(views::CreateRoundedRectBackground(
+      ColorProvider::Get()->GetBaseLayerColor(
+          ColorProvider::BaseLayerType::kTransparent80),
+      GetCornerRadius(/*tablet_mode=*/true)));
+  SetBorder(std::make_unique<HighlightBorder>(
+      GetCornerRadius(/*tablet_mode=*/true),
+      HighlightBorder::Type::kHighlightBorder2,
+      /*use_light_colors=*/false));
+}
+
 BEGIN_METADATA(ContinueTaskView, views::View)
 END_METADATA
 
diff --git a/ash/app_list/views/continue_task_view.h b/ash/app_list/views/continue_task_view.h
index 737e987..490d7ec 100644
--- a/ash/app_list/views/continue_task_view.h
+++ b/ash/app_list/views/continue_task_view.h
@@ -90,6 +90,10 @@
   // Closes the context menu for this view if it is running.
   void CloseContextMenu();
 
+  // Updates the background and the border if the ContinueTaskView is in tablet
+  // mode.
+  void UpdateStyleForTabletMode();
+
   // The index of this view within a |SearchResultContainerView| that holds it.
   absl::optional<int> index_in_container_;
 
@@ -99,6 +103,8 @@
   views::ImageView* icon_ = nullptr;
   SearchResult* result_ = nullptr;  // Owned by SearchModel::SearchResults.
 
+  const bool is_tablet_mode_;
+
   std::unique_ptr<ui::SimpleMenuModel> context_menu_model_;
   std::unique_ptr<views::MenuRunner> context_menu_runner_;
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 046c330..8cf977b 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -3474,6 +3474,10 @@
         S
       </message>
 
+      <message name="IDS_ASH_CALENDAR_NO_EVENTS" desc="Text that appears on the event list when there are no events.">
+        Open in Google calendar
+      </message>
+
       <!-- Power off menu  -->
       <message name="IDS_ASH_POWER_BUTTON_MENU_POWER_OFF_BUTTON" desc="Text shown on power off button in power button menu.">
         Shut down
diff --git a/ash/ash_strings_grd/IDS_ASH_CALENDAR_NO_EVENTS.png.sha1 b/ash/ash_strings_grd/IDS_ASH_CALENDAR_NO_EVENTS.png.sha1
new file mode 100644
index 0000000..99217f8
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_CALENDAR_NO_EVENTS.png.sha1
@@ -0,0 +1 @@
+d8eed11d7af76ee7413e3db47a3336946a49a86e
\ No newline at end of file
diff --git a/ash/components/arc/BUILD.gn b/ash/components/arc/BUILD.gn
index bec3a0a..9c7667d 100644
--- a/ash/components/arc/BUILD.gn
+++ b/ash/components/arc/BUILD.gn
@@ -306,6 +306,8 @@
     "test/fake_policy_instance.h",
     "test/fake_power_instance.cc",
     "test/fake_power_instance.h",
+    "test/fake_privacy_items_instance.cc",
+    "test/fake_privacy_items_instance.h",
     "test/fake_process_instance.cc",
     "test/fake_process_instance.h",
     "test/fake_sharesheet_instance.cc",
diff --git a/ash/components/arc/test/fake_privacy_items_instance.cc b/ash/components/arc/test/fake_privacy_items_instance.cc
new file mode 100644
index 0000000..7d0692e
--- /dev/null
+++ b/ash/components/arc/test/fake_privacy_items_instance.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium 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/components/arc/test/fake_privacy_items_instance.h"
+
+#include <utility>
+
+namespace arc {
+
+FakePrivacyItemsInstance::FakePrivacyItemsInstance() = default;
+
+FakePrivacyItemsInstance::~FakePrivacyItemsInstance() = default;
+
+void FakePrivacyItemsInstance::Init(
+    mojo::PendingRemote<mojom::PrivacyItemsHost> host_remote,
+    InitCallback callback) {
+  host_remote_.reset();
+  host_remote_.Bind(std::move(host_remote));
+  std::move(callback).Run();
+}
+
+void FakePrivacyItemsInstance::OnStaticPrivacyIndicatorBoundsChanged(
+    int32_t display_id,
+    const std::vector<gfx::Rect>& bounds) {
+  last_bounds_display_id_ = display_id;
+  last_bounds_ = bounds;
+}
+
+}  // namespace arc
diff --git a/ash/components/arc/test/fake_privacy_items_instance.h b/ash/components/arc/test/fake_privacy_items_instance.h
new file mode 100644
index 0000000..3b2aae5
--- /dev/null
+++ b/ash/components/arc/test/fake_privacy_items_instance.h
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_COMPONENTS_ARC_TEST_FAKE_PRIVACY_ITEMS_INSTANCE_H_
+#define ASH_COMPONENTS_ARC_TEST_FAKE_PRIVACY_ITEMS_INSTANCE_H_
+
+#include "ash/components/arc/mojom/privacy_items.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "ui/display/display.h"
+
+namespace arc {
+
+class FakePrivacyItemsInstance : public mojom::PrivacyItemsInstance {
+ public:
+  FakePrivacyItemsInstance();
+
+  FakePrivacyItemsInstance(const FakePrivacyItemsInstance&) = delete;
+  FakePrivacyItemsInstance& operator=(const FakePrivacyItemsInstance&) = delete;
+
+  ~FakePrivacyItemsInstance() override;
+
+  int last_bounds_display_id() const { return last_bounds_display_id_; }
+  std::vector<gfx::Rect> last_bounds() const { return last_bounds_; }
+
+  // mojom::PrivacyItemsInstance overrides:
+  void Init(mojo::PendingRemote<mojom::PrivacyItemsHost> host_remote,
+            InitCallback callback) override;
+  void OnStaticPrivacyIndicatorBoundsChanged(
+      int32_t display_id,
+      const std::vector<gfx::Rect>& bounds) override;
+
+ private:
+  mojo::Remote<mojom::PrivacyItemsHost> host_remote_;
+  int32_t last_bounds_display_id_ = display::kInvalidDisplayId;
+  std::vector<gfx::Rect> last_bounds_;
+};
+
+}  // namespace arc
+
+#endif  // ASH_COMPONENTS_ARC_TEST_FAKE_PRIVACY_ITEMS_INSTANCE_H_
diff --git a/ash/components/fwupd/BUILD.gn b/ash/components/fwupd/BUILD.gn
index ebaf724..601e019 100644
--- a/ash/components/fwupd/BUILD.gn
+++ b/ash/components/fwupd/BUILD.gn
@@ -21,6 +21,8 @@
   sources = [
     "firmware_update_manager.cc",
     "firmware_update_manager.h",
+    "histogram_util.cc",
+    "histogram_util.h",
   ]
 }
 
diff --git a/ash/components/fwupd/firmware_update_manager.cc b/ash/components/fwupd/firmware_update_manager.cc
index 3eb60e3..860dc074 100644
--- a/ash/components/fwupd/firmware_update_manager.cc
+++ b/ash/components/fwupd/firmware_update_manager.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <utility>
 
+#include "ash/components/fwupd/histogram_util.h"
 #include "ash/public/cpp/fwupd_download_client.h"
 #include "ash/webui/firmware_update_ui/mojom/firmware_update.mojom.h"
 #include "base/base_paths.h"
@@ -240,6 +241,10 @@
   }
 }
 
+void FirmwareUpdateManager::RecordDeviceMetrics(int num_devices) {
+  firmware_update::metrics::EmitDeviceCount(num_devices, is_first_response_);
+}
+
 void FirmwareUpdateManager::NotifyUpdateListObservers() {
   for (auto& observer : update_list_observers_) {
     observer->OnUpdateListChanged(mojo::Clone(updates_));
@@ -450,6 +455,8 @@
   // Clear all cached updates prior to fetching the new update list.
   updates_.clear();
 
+  RecordDeviceMetrics(devices->size());
+
   // Fire the observer with an empty list if there are no devices in the
   // response.
   if (devices->empty()) {
@@ -464,7 +471,6 @@
 }
 
 void FirmwareUpdateManager::ShowNotificationIfRequired() {
-  should_show_notification_ = false;
   for (const auto& update : updates_) {
     if (update->priority == firmware_update::mojom::UpdatePriority::kCritical &&
         !base::Contains(devices_already_notified_, update->device_id)) {
@@ -490,13 +496,19 @@
   // Remove the pending device.
   devices_pending_update_.erase(device_id);
 
-  // Fire the observer if there are no devices pending updates.
-  if (!HasPendingUpdates()) {
-    if (should_show_notification_) {
-      ShowNotificationIfRequired();
-    }
-    NotifyUpdateListObservers();
+  if (HasPendingUpdates()) {
+    return;
   }
+
+  // We only want to show the notification once, at startup.
+  if (is_first_response_) {
+    ShowNotificationIfRequired();
+  }
+
+  is_first_response_ = false;
+
+  // Fire the observer since there are no remaining devices pending updates.
+  NotifyUpdateListObservers();
 }
 
 void FirmwareUpdateManager::OnInstallResponse(bool success) {
diff --git a/ash/components/fwupd/firmware_update_manager.h b/ash/components/fwupd/firmware_update_manager.h
index 5d40d41..fdd6be6 100644
--- a/ash/components/fwupd/firmware_update_manager.h
+++ b/ash/components/fwupd/firmware_update_manager.h
@@ -169,6 +169,10 @@
   // Call to notify observers that a new notification is needed.
   void NotifyCriticalFirmwareUpdateReceived();
 
+  // Records the # of devices found at startup and whenever the device list
+  // is refreshed.
+  void RecordDeviceMetrics(int num_devices);
+
   // Map of a device ID to `FwupdDevice` which is waiting for the list
   // of updates.
   base::flat_map<std::string, chromeos::FwupdDevice> devices_pending_update_;
@@ -187,8 +191,9 @@
   // The device update that is currently inflight.
   firmware_update::mojom::FirmwareUpdatePtr inflight_update_;
 
-  // We only want to show the notification once, at startup.
-  bool should_show_notification_ = true;
+  // Used to show the firmware update notification and to determine which
+  // metric to fire (Startup/Refresh).
+  bool is_first_response_ = true;
 
   // Whether or not fetching updates in inflight.
   bool is_fetching_updates_ = false;
diff --git a/ash/components/fwupd/firmware_update_manager_unittest.cc b/ash/components/fwupd/firmware_update_manager_unittest.cc
index e4a6d483..21c7f16d 100644
--- a/ash/components/fwupd/firmware_update_manager_unittest.cc
+++ b/ash/components/fwupd/firmware_update_manager_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chromeos/dbus/fwupd/fwupd_client.h"
@@ -209,7 +210,10 @@
     return firmware_update_manager_->GetNumUpdatesForTesting();
   }
 
-  void RequestDevices() { firmware_update_manager_->RequestDevices(); }
+  void RequestDevices() {
+    firmware_update_manager_->RequestDevices();
+    base::RunLoop().RunUntilIdle();
+  }
 
   void SetFakeUrlForTesting(const std::string& fake_url) {
     firmware_update_manager_->SetFakeUrlForTesting(fake_url);
@@ -556,7 +560,6 @@
   dbus_responses_.push_back(CreateOneUpdateResponse());
 
   RequestDevices();
-  base::RunLoop().RunUntilIdle();
 
   // Expect cache to clear and only 1 updates now instead of 2.
   const std::vector<firmware_update::mojom::FirmwareUpdatePtr>& new_updates =
@@ -762,4 +765,21 @@
   SetupObserver(&update_observer);
   EXPECT_EQ(0, message_center_->notification_count());
 }
+
+TEST_F(FirmwareUpdateManagerTest, DeviceCountMetric) {
+  base::HistogramTester histogram_tester;
+  EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
+      .WillRepeatedly(Invoke(this, &FirmwareUpdateManagerTest::OnMethodCalled));
+  dbus_responses_.push_back(CreateOneDeviceResponse());
+  dbus_responses_.push_back(CreateOneUpdateResponse());
+  dbus_responses_.push_back(CreateOneDeviceResponse());
+  dbus_responses_.push_back(CreateOneUpdateResponse());
+  FakeUpdateObserver update_observer;
+  SetupObserver(&update_observer);
+  histogram_tester.ExpectUniqueSample(
+      "ChromeOS.FirmwareUpdateUi.OnStartup.DeviceCount", 1, 1);
+  RequestDevices();
+  histogram_tester.ExpectUniqueSample(
+      "ChromeOS.FirmwareUpdateUi.OnRefresh.DeviceCount", 1, 1);
+}
 }  // namespace ash
diff --git a/ash/components/fwupd/histogram_util.cc b/ash/components/fwupd/histogram_util.cc
new file mode 100644
index 0000000..c3c86ff
--- /dev/null
+++ b/ash/components/fwupd/histogram_util.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium 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/components/fwupd/histogram_util.h"
+
+#include <string>
+
+#include "base/metrics/histogram_functions.h"
+
+namespace {
+
+const char kHistogramName[] = "ChromeOS.FirmwareUpdateUi.";
+
+}  // namespace
+namespace ash {
+namespace firmware_update {
+namespace metrics {
+void EmitDeviceCount(int num_devices, bool is_startup) {
+  base::UmaHistogramCounts100(
+      GetSourceStr(is_startup) + std::string(".DeviceCount"), num_devices);
+}
+
+std::string GetSourceStr(bool is_startup) {
+  return std::string(kHistogramName) +
+         std::string(is_startup ? "OnStartup" : "OnRefresh");
+}
+}  // namespace metrics
+}  // namespace firmware_update
+}  // namespace ash
diff --git a/ash/components/fwupd/histogram_util.h b/ash/components/fwupd/histogram_util.h
new file mode 100644
index 0000000..455d9c714
--- /dev/null
+++ b/ash/components/fwupd/histogram_util.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_COMPONENTS_FWUPD_HISTOGRAM_UTIL_H_
+#define ASH_COMPONENTS_FWUPD_HISTOGRAM_UTIL_H_
+
+#include <cstdint>
+#include <string>
+
+namespace ash {
+namespace firmware_update {
+namespace metrics {
+
+void EmitDeviceCount(int num_devices, bool is_startup);
+
+std::string GetSourceStr(bool is_startup);
+
+}  // namespace metrics
+}  // namespace firmware_update
+}  // namespace ash
+
+#endif  // ASH_COMPONENTS_FWUPD_HISTOGRAM_UTIL_H_
diff --git a/ash/components/phonehub/proto/phonehub_api.proto b/ash/components/phonehub/proto/phonehub_api.proto
index a01b102..423e7757 100644
--- a/ash/components/phonehub/proto/phonehub_api.proto
+++ b/ash/components/phonehub/proto/phonehub_api.proto
@@ -100,6 +100,12 @@
   WORK_PROFILE = 1;
 }
 
+enum ProfileDisableReason {
+  DISABLE_REASON_UNKNOWN = 0;
+  DISABLE_REASON_NOT_SUPPORTED = 1;
+  DISABLE_REASON_DISABLED_BY_POLICY = 2;
+}
+
 enum DoNotDisturbCapability {
   DO_NOT_DISTURB_NORMAL = 0;
   DO_NOT_DISTURB_NOT_ALLOWED = 1;
@@ -147,6 +153,7 @@
   FindMyDeviceRingStatus ring_status = 9;
 
   ProfileType profile_type = 10;
+  ProfileDisableReason profile_disable_reason = 17;
 
   FindMyDeviceCapability find_my_device_capability = 11;
 
diff --git a/ash/public/cpp/accessibility_controller.h b/ash/public/cpp/accessibility_controller.h
index f45a829..89b0397 100644
--- a/ash/public/cpp/accessibility_controller.h
+++ b/ash/public/cpp/accessibility_controller.h
@@ -190,7 +190,8 @@
   virtual void UpdateDictationBubble(
       bool visible,
       DictationBubbleIconType icon,
-      const absl::optional<std::u16string>& text) = 0;
+      const absl::optional<std::u16string>& text,
+      const absl::optional<std::vector<std::string>>& hints) = 0;
 
  protected:
   AccessibilityController();
diff --git a/ash/style/system_shadow.cc b/ash/style/system_shadow.cc
new file mode 100644
index 0000000..53cdbfe
--- /dev/null
+++ b/ash/style/system_shadow.cc
@@ -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.
+
+#include "ash/style/system_shadow.h"
+#include "base/logging.h"
+#include "base/notreached.h"
+
+namespace ash {
+
+SystemShadow::SystemShadow(Type type) : type_(type) {
+  // Note: Init function should be called before SetShadowStyle.
+  Init(GetElevationFromType(type_));
+  // System shadow always use `kChromeOSSystemUI` as shadow style.
+  SetShadowStyle(gfx::ShadowStyle::kChromeOSSystemUI);
+}
+
+SystemShadow::~SystemShadow() = default;
+
+int SystemShadow::GetElevationFromType(Type type) {
+  switch (type) {
+    case Type::kElevation4:
+      return 4;
+    case Type::kElevation8:
+      return 8;
+    case Type::kElevation12:
+      return 12;
+    case Type::kElevation16:
+      return 16;
+    case Type::kElevation24:
+      return 24;
+  }
+}
+
+void SystemShadow::SetType(Type type) {
+  if (type_ == type)
+    return;
+
+  type_ = type;
+  SetElevation(GetElevationFromType(type_));
+}
+
+}  // namespace ash
diff --git a/ash/style/system_shadow.h b/ash/style/system_shadow.h
new file mode 100644
index 0000000..4c3c4c9
--- /dev/null
+++ b/ash/style/system_shadow.h
@@ -0,0 +1,46 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_STYLE_SYSTEM_SHADOW_H_
+#define ASH_STYLE_SYSTEM_SHADOW_H_
+
+#include "ash/ash_export.h"
+#include "ui/compositor_extra/shadow.h"
+
+namespace ash {
+
+// Shadow for Chrome OS System UI component.
+class ASH_EXPORT SystemShadow : public ui::Shadow {
+ public:
+  // Shadow types of system UI components. The shadows with different elevations
+  // have different appearance.
+  enum class Type {
+    kElevation4,
+    kElevation8,
+    kElevation12,
+    kElevation16,
+    kElevation24,
+  };
+
+  explicit SystemShadow(Type type);
+  SystemShadow(const SystemShadow&) = delete;
+  SystemShadow& operator=(const SystemShadow&) = delete;
+  ~SystemShadow() override;
+
+  // Get shadow elevation according to the given type.
+  static int GetElevationFromType(Type type);
+
+  // Change shadow type and update shadow elevation and appearance. Note that to
+  // avoid inconsistency of shadow type and elevation. Always change system
+  // shadow elevation with `SetType` instead of `SetElevation`.
+  void SetType(Type type);
+  Type type() const { return type_; }
+
+ private:
+  Type type_ = Type::kElevation4;
+};
+
+}  // namespace ash
+
+#endif  // ASH_STYLE_SYSTEM_SHADOW_H_
diff --git a/ash/system/accessibility/dictation_bubble_controller.cc b/ash/system/accessibility/dictation_bubble_controller.cc
index 8f87a60..f33cb23 100644
--- a/ash/system/accessibility/dictation_bubble_controller.cc
+++ b/ash/system/accessibility/dictation_bubble_controller.cc
@@ -32,13 +32,14 @@
 void DictationBubbleController::UpdateBubble(
     bool visible,
     DictationBubbleIconType icon,
-    const absl::optional<std::u16string>& text) {
+    const absl::optional<std::u16string>& text,
+    const absl::optional<std::vector<std::string>>& hints) {
   if (visible) {
     MaybeInitialize();
-    Update(icon, text);
+    Update(icon, text, hints);
     widget_->Show();
   } else {
-    Update(icon, text);
+    Update(icon, text, hints);
     if (widget_) {
       widget_->Hide();
     }
@@ -85,12 +86,13 @@
 // Chrome tab.
 void DictationBubbleController::Update(
     DictationBubbleIconType icon,
-    const absl::optional<std::u16string>& text) {
+    const absl::optional<std::u16string>& text,
+    const absl::optional<std::vector<std::string>>& hints) {
   DCHECK(dictation_bubble_view_);
   DCHECK(widget_);
 
   // Update `dictation_bubble_view_`.
-  dictation_bubble_view_->Update(icon, text);
+  dictation_bubble_view_->Update(icon, text, hints);
 
   // Update the bounds to fit entirely within the screen.
   gfx::Rect new_bounds = widget_->GetWindowBoundsInScreen();
diff --git a/ash/system/accessibility/dictation_bubble_controller.h b/ash/system/accessibility/dictation_bubble_controller.h
index 5866db0b..d3cf84366 100644
--- a/ash/system/accessibility/dictation_bubble_controller.h
+++ b/ash/system/accessibility/dictation_bubble_controller.h
@@ -42,7 +42,8 @@
   // Updates the bubble's visibility and text content.
   void UpdateBubble(bool visible,
                     DictationBubbleIconType icon,
-                    const absl::optional<std::u16string>& text);
+                    const absl::optional<std::u16string>& text,
+                    const absl::optional<std::vector<std::string>>& hints);
 
   // ui::InputMethodObserver:
   void OnFocus() override {}
@@ -64,7 +65,8 @@
 
   // Updates the view and widget.
   void Update(DictationBubbleIconType icon,
-              const absl::optional<std::u16string>& text);
+              const absl::optional<std::u16string>& text,
+              const absl::optional<std::vector<std::string>>& hints);
 
   // Owned by views hierarchy.
   DictationBubbleView* dictation_bubble_view_ = nullptr;
diff --git a/ash/system/accessibility/dictation_bubble_controller_unittest.cc b/ash/system/accessibility/dictation_bubble_controller_unittest.cc
index 69a97c6ab..b06466d 100644
--- a/ash/system/accessibility/dictation_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/dictation_bubble_controller_unittest.cc
@@ -40,15 +40,17 @@
   }
 
   void Show(DictationBubbleIconType icon,
-            const absl::optional<std::u16string>& text) {
+            const absl::optional<std::u16string>& text,
+            const absl::optional<std::vector<std::string>>& hints) {
     GetController()->UpdateBubble(
-        /*visible=*/true, /*icon=*/icon, /*text=*/text);
+        /*visible=*/true, /*icon=*/icon, /*text=*/text, /*hints=*/hints);
   }
 
   void Hide() {
     GetController()->UpdateBubble(/*visible=*/false,
                                   /*icon=*/DictationBubbleIconType::kHidden,
-                                  /*text=*/std::u16string());
+                                  /*text=*/std::u16string(),
+                                  /*hints=*/std::vector<std::string>());
   }
 
   DictationBubbleView* GetView() {
@@ -89,8 +91,8 @@
     return GetView()->GetLabelTextColorForTesting();
   }
 
-  int GetVisibleHintsCount() {
-    return GetView()->GetVisibleHintsCountForTesting();
+  std::vector<std::u16string> GetVisibleHints() {
+    return GetView()->GetVisibleHintsForTesting();
   }
 
  private:
@@ -100,7 +102,8 @@
 TEST_F(DictationBubbleControllerTest, ShowText) {
   EXPECT_FALSE(GetView());
   Show(DictationBubbleIconType::kHidden,
-       absl::optional<std::u16string>(u"Testing"));
+       absl::optional<std::u16string>(u"Testing"),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
   EXPECT_EQ(u"Testing", GetBubbleText());
@@ -113,7 +116,8 @@
 
 TEST_F(DictationBubbleControllerTest, ShowStandbyImage) {
   EXPECT_FALSE(GetView());
-  Show(DictationBubbleIconType::kStandby, absl::optional<std::u16string>());
+  Show(DictationBubbleIconType::kStandby, absl::optional<std::u16string>(),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
   EXPECT_EQ(std::u16string(), GetBubbleText());
@@ -127,7 +131,8 @@
 TEST_F(DictationBubbleControllerTest, ShowMacroSuccessImage) {
   EXPECT_FALSE(GetView());
   Show(DictationBubbleIconType::kMacroSuccess,
-       absl::optional<std::u16string>(u"Macro successfull"));
+       absl::optional<std::u16string>(u"Macro successfull"),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
   EXPECT_EQ(u"Macro successfull", GetBubbleText());
@@ -141,7 +146,8 @@
 TEST_F(DictationBubbleControllerTest, ShowMacroFailImage) {
   EXPECT_FALSE(GetView());
   Show(DictationBubbleIconType::kMacroFail,
-       absl::optional<std::u16string>(u"Macro failed"));
+       absl::optional<std::u16string>(u"Macro failed"),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
   EXPECT_EQ(u"Macro failed", GetBubbleText());
@@ -166,7 +172,8 @@
   // Show bubble UI.
   EXPECT_FALSE(GetView());
   Show(DictationBubbleIconType::kHidden,
-       absl::optional<std::u16string>(u"Testing"));
+       absl::optional<std::u16string>(u"Testing"),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
   EXPECT_EQ(u"Testing", GetBubbleText());
@@ -190,11 +197,12 @@
 
 TEST_F(DictationBubbleControllerTest, Hints) {
   EXPECT_FALSE(GetView());
-  Show(DictationBubbleIconType::kStandby, absl::optional<std::u16string>());
+  Show(DictationBubbleIconType::kStandby, absl::optional<std::u16string>(),
+       absl::optional<std::vector<std::string>>());
   EXPECT_TRUE(GetView());
   EXPECT_TRUE(IsBubbleVisible());
 
-  EXPECT_EQ(0, GetVisibleHintsCount());
+  EXPECT_TRUE(GetVisibleHints().size() == 0);
 
   HideAndCheckExpectations();
 }
diff --git a/ash/system/accessibility/dictation_bubble_view.cc b/ash/system/accessibility/dictation_bubble_view.cc
index 07fb055..7c862d6 100644
--- a/ash/system/accessibility/dictation_bubble_view.cc
+++ b/ash/system/accessibility/dictation_bubble_view.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/ash_color_provider.h"
+#include "base/strings/utf_string_conversions.h"
 #include "cc/paint/skottie_wrapper.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -200,11 +201,16 @@
   ~HintView() override = default;
 
   // Updates the text content and visibility of all labels in this view.
-  void Update(const std::vector<std::u16string>& hints) {
+  void Update(const absl::optional<std::vector<std::string>>& hints) {
+    if (hints.has_value())
+      DCHECK(hints.value().size() <= 5);
+
     for (size_t i = 0; i < labels_.size(); ++i) {
-      bool has_hint_for_index = i < hints.size();
+      bool has_hint_for_index = hints.has_value() && (i < hints.value().size());
       labels_[i]->SetVisible(has_hint_for_index ? true : false);
-      labels_[i]->SetText(has_hint_for_index ? hints[i] : std::u16string());
+      labels_[i]->SetText(has_hint_for_index
+                              ? base::UTF8ToUTF16(hints.value()[i])
+                              : std::u16string());
     }
     SizeToPreferredSize();
   }
@@ -250,10 +256,12 @@
 
 DictationBubbleView::~DictationBubbleView() = default;
 
-void DictationBubbleView::Update(DictationBubbleIconType icon,
-                                 const absl::optional<std::u16string>& text) {
+void DictationBubbleView::Update(
+    DictationBubbleIconType icon,
+    const absl::optional<std::u16string>& text,
+    const absl::optional<std::vector<std::string>>& hints) {
   top_row_view_->Update(icon, text);
-  hint_view_->Update(std::vector<std::u16string>());
+  hint_view_->Update(hints);
   SizeToContents();
 }
 
@@ -313,13 +321,14 @@
   return top_row_view_->label_->GetEnabledColor();
 }
 
-int DictationBubbleView::GetVisibleHintsCountForTesting() {
-  int count = 0;
+std::vector<std::u16string> DictationBubbleView::GetVisibleHintsForTesting() {
+  std::vector<std::u16string> hints;
   for (size_t i = 0; i < hint_view_->labels_.size(); ++i) {
-    if (hint_view_->labels_[i]->GetVisible())
-      ++count;
+    views::Label* label = hint_view_->labels_[i];
+    if (label->GetVisible())
+      hints.push_back(label->GetText());
   }
-  return count;
+  return hints;
 }
 
 BEGIN_METADATA(DictationBubbleView, views::View)
diff --git a/ash/system/accessibility/dictation_bubble_view.h b/ash/system/accessibility/dictation_bubble_view.h
index 7c7436b..f02d615 100644
--- a/ash/system/accessibility/dictation_bubble_view.h
+++ b/ash/system/accessibility/dictation_bubble_view.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_ACCESSIBILITY_DICTATION_BUBBLE_VIEW_H_
 
 #include <string>
+#include <vector>
 
 #include "ash/ash_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -36,10 +37,11 @@
   DictationBubbleView& operator=(const DictationBubbleView&) = delete;
   ~DictationBubbleView() override;
 
-  // Updates the visibility of all child views. Also updates the text content
-  // of `label_` and updates the size of this view.
+  // Updates the visibility of all child views, displays the icon/animation
+  // specified by `icon`, and updates text content and size of this view.
   void Update(DictationBubbleIconType icon,
-              const absl::optional<std::u16string>& text);
+              const absl::optional<std::u16string>& text,
+              const absl::optional<std::vector<std::string>>& hints);
 
   void OnColorModeChanged(bool dark_mode_enabled);
 
@@ -57,7 +59,7 @@
   bool IsMacroFailedImageVisibleForTesting();
   SkColor GetLabelBackgroundColorForTesting();
   SkColor GetLabelTextColorForTesting();
-  int GetVisibleHintsCountForTesting();
+  std::vector<std::u16string> GetVisibleHintsForTesting();
 
  private:
   friend class DictationBubbleControllerTest;
diff --git a/ash/system/holding_space/holding_space_animation_registry.cc b/ash/system/holding_space/holding_space_animation_registry.cc
index f2aa63cad..dc9aad5 100644
--- a/ash/system/holding_space/holding_space_animation_registry.cc
+++ b/ash/system/holding_space/holding_space_animation_registry.cc
@@ -125,7 +125,7 @@
       HoldingSpaceProgressRingAnimation::Type type) {
     auto it = registry_->ring_animations_by_key().find(key);
     if (it != registry_->ring_animations_by_key().end() &&
-        it->second.animation->type() != type) {
+        it->second->type() != type) {
       registry_->SetProgressRingAnimationForKey(key, nullptr);
     }
   }
@@ -153,12 +153,8 @@
       return;
 
     registry_->icon_animations_by_key()
-        .emplace(key,
-                 AnimationWithSubscription<HoldingSpaceProgressIconAnimation>{
-                     .animation =
-                         std::make_unique<HoldingSpaceProgressIconAnimation>(),
-                     .subscription = base::CallbackListSubscription()})
-        .first->second.animation->Start();
+        .emplace(key, std::make_unique<HoldingSpaceProgressIconAnimation>())
+        .first->second->Start();
 
     NotifyIconAnimationChangedForKey(key);
   }
@@ -171,29 +167,22 @@
       HoldingSpaceProgressRingAnimation::Type type) {
     auto it = registry_->ring_animations_by_key().find(key);
     if (it != registry_->ring_animations_by_key().end() &&
-        it->second.animation->type() == type) {
+        it->second->type() == type) {
       return;
     }
 
     auto animation = HoldingSpaceProgressRingAnimation::CreateOfType(type);
-
-    auto subscription =
-        animation->AddAnimationUpdatedCallback(base::BindRepeating(
-            &ProgressIndicatorAnimationDelegate::OnRingAnimationUpdatedForKey,
-            base::Unretained(this), key, animation.get()));
-
-    auto subscribed_animation =
-        AnimationWithSubscription<HoldingSpaceProgressRingAnimation>{
-            .animation = std::move(animation),
-            .subscription = std::move(subscription)};
+    animation->AddUnsafeAnimationUpdatedCallback(base::BindRepeating(
+        &ProgressIndicatorAnimationDelegate::OnRingAnimationUpdatedForKey,
+        base::Unretained(this), key, animation.get()));
 
     if (it == registry_->ring_animations_by_key().end()) {
       registry_->ring_animations_by_key()
-          .emplace(key, std::move(subscribed_animation))
-          .first->second.animation->Start();
+          .emplace(key, std::move(animation))
+          .first->second->Start();
     } else {
-      it->second = std::move(subscribed_animation);
-      it->second.animation->Start();
+      it->second = std::move(animation);
+      it->second->Start();
     }
 
     NotifyRingAnimationChangedForKey(key);
@@ -208,7 +197,7 @@
       return;
     auto animation_it = registry_->icon_animations_by_key().find(key);
     it->second.Notify(animation_it != registry_->icon_animations_by_key().end()
-                          ? animation_it->second.animation.get()
+                          ? animation_it->second.get()
                           : nullptr);
   }
 
@@ -221,7 +210,7 @@
       return;
     auto animation_it = registry_->ring_animations_by_key().find(key);
     it->second.Notify(animation_it != registry_->ring_animations_by_key().end()
-                          ? animation_it->second.animation.get()
+                          ? animation_it->second.get()
                           : nullptr);
   }
 
diff --git a/ash/system/holding_space/holding_space_progress_indicator_animation.cc b/ash/system/holding_space/holding_space_progress_indicator_animation.cc
index 3f90236..7d50d79 100644
--- a/ash/system/holding_space/holding_space_progress_indicator_animation.cc
+++ b/ash/system/holding_space/holding_space_progress_indicator_animation.cc
@@ -24,6 +24,11 @@
   return animation_updated_callback_list_.Add(std::move(callback));
 }
 
+void HoldingSpaceProgressIndicatorAnimation::AddUnsafeAnimationUpdatedCallback(
+    base::RepeatingClosureList::CallbackType callback) {
+  animation_updated_callback_list_.AddUnsafe(std::move(callback));
+}
+
 void HoldingSpaceProgressIndicatorAnimation::Start() {
   StartInternal(/*is_cyclic_restart=*/false);
 }
diff --git a/ash/system/holding_space/holding_space_progress_indicator_animation.h b/ash/system/holding_space/holding_space_progress_indicator_animation.h
index 0551465..0d99f3b 100644
--- a/ash/system/holding_space/holding_space_progress_indicator_animation.h
+++ b/ash/system/holding_space/holding_space_progress_indicator_animation.h
@@ -34,6 +34,14 @@
   base::CallbackListSubscription AddAnimationUpdatedCallback(
       base::RepeatingClosureList::CallbackType callback);
 
+  // Adds the specified `callback` to be notified of animation updates.
+  // NOTE: Because no subscription is returned by which to remove `callback`,
+  // this method should only be used where the `callback` is guaranteed to live
+  // at least as long as `this`. When lifetime is in doubt, prefer the use of
+  // `AddAnimationUpdatedCallback()`.
+  void AddUnsafeAnimationUpdatedCallback(
+      base::RepeatingClosureList::CallbackType callback);
+
   // Immediately starts this animation.
   void Start();
 
diff --git a/ash/system/progress_indicator/progress_indicator_animation_registry.cc b/ash/system/progress_indicator/progress_indicator_animation_registry.cc
index 87abd684..030e71f 100644
--- a/ash/system/progress_indicator/progress_indicator_animation_registry.cc
+++ b/ash/system/progress_indicator/progress_indicator_animation_registry.cc
@@ -12,10 +12,7 @@
 // Type aliases ----------------------------------------------------------------
 
 template <typename AnimationType>
-using KeyedAnimationWithSubscriptionMap =
-    std::map<const void*,
-             ProgressIndicatorAnimationRegistry::AnimationWithSubscription<
-                 AnimationType>>;
+using KeyedAnimationMap = std::map<const void*, std::unique_ptr<AnimationType>>;
 
 template <typename CallbackListType>
 using KeyedAnimationChangedCallbackListMap =
@@ -27,7 +24,7 @@
 // that the associated animation has changed.
 template <typename AnimationType, typename CallbackListType>
 void NotifyAnimationChangedForKey(
-    KeyedAnimationWithSubscriptionMap<AnimationType>* animations_by_key,
+    KeyedAnimationMap<AnimationType>* animations_by_key,
     KeyedAnimationChangedCallbackListMap<CallbackListType>*
         animation_changed_callback_lists_by_key,
     const void* key) {
@@ -36,7 +33,7 @@
     return;
   auto animations_it = animations_by_key->find(key);
   callback_lists_it->second.Notify(animations_it != animations_by_key->end()
-                                       ? animations_it->second.animation.get()
+                                       ? animations_it->second.get()
                                        : nullptr);
 }
 
@@ -81,10 +78,10 @@
 // * `GetProgressRingAnimationForKey()`
 template <typename AnimationType>
 AnimationType* GetAnimationForKey(
-    KeyedAnimationWithSubscriptionMap<AnimationType>* animations_by_key,
+    KeyedAnimationMap<AnimationType>* animations_by_key,
     const void* key) {
   auto it = animations_by_key->find(key);
-  return it != animations_by_key->end() ? it->second.animation.get() : nullptr;
+  return it != animations_by_key->end() ? it->second.get() : nullptr;
 }
 
 // Implements:
@@ -92,17 +89,14 @@
 // * `SetProgressRingAnimationForKey()`
 template <typename AnimationType, typename CallbackListType>
 AnimationType* SetAnimationForKey(
-    KeyedAnimationWithSubscriptionMap<AnimationType>* animations_by_key,
+    KeyedAnimationMap<AnimationType>* animations_by_key,
     KeyedAnimationChangedCallbackListMap<CallbackListType>*
         animation_changed_callback_lists_by_key,
     const void* key,
     std::unique_ptr<AnimationType> animation) {
   AnimationType* animation_ptr = animation.get();
   if (animation) {
-    (*animations_by_key)[key] =
-        ProgressIndicatorAnimationRegistry::AnimationWithSubscription<
-            AnimationType>{.animation = std::move(animation),
-                           .subscription = base::CallbackListSubscription()};
+    (*animations_by_key)[key] = std::move(animation);
     NotifyAnimationChangedForKey(animations_by_key,
                                  animation_changed_callback_lists_by_key, key);
   } else {
diff --git a/ash/system/progress_indicator/progress_indicator_animation_registry.h b/ash/system/progress_indicator/progress_indicator_animation_registry.h
index 138935ca..6c7c195 100644
--- a/ash/system/progress_indicator/progress_indicator_animation_registry.h
+++ b/ash/system/progress_indicator/progress_indicator_animation_registry.h
@@ -95,18 +95,9 @@
   void EraseAllAnimationsForKeyIf(
       base::RepeatingCallback<bool(const void* key)> predicate);
 
-  // TODO(dmblack): Move to private after completing refactor of
-  // `ProgressIndicatorAnimationRegistry` and `HoldingSpaceAnimationRegistry`.
-  template <typename AnimationType>
-  struct AnimationWithSubscription {
-    std::unique_ptr<AnimationType> animation;
-    base::CallbackListSubscription subscription;
-  };
-
   // TODO(dmblack): Remove these methods after completing refactor of
   // `ProgressIndicatorAnimationRegistry` and `HoldingSpaceAnimationRegistry`.
-  std::map<const void*,
-           AnimationWithSubscription<HoldingSpaceProgressIconAnimation>>&
+  std::map<const void*, std::unique_ptr<HoldingSpaceProgressIconAnimation>>&
   icon_animations_by_key() {
     return icon_animations_by_key_;
   }
@@ -114,8 +105,7 @@
   icon_animation_changed_callback_lists_by_key() {
     return icon_animation_changed_callback_lists_by_key_;
   }
-  std::map<const void*,
-           AnimationWithSubscription<HoldingSpaceProgressRingAnimation>>&
+  std::map<const void*, std::unique_ptr<HoldingSpaceProgressRingAnimation>>&
   ring_animations_by_key() {
     return ring_animations_by_key_;
   }
@@ -126,8 +116,7 @@
 
  private:
   // Mapping of keys to their associated progress icon animations.
-  std::map<const void*,
-           AnimationWithSubscription<HoldingSpaceProgressIconAnimation>>
+  std::map<const void*, std::unique_ptr<HoldingSpaceProgressIconAnimation>>
       icon_animations_by_key_;
 
   // Mapping of keys to their associated icon animation changed callback lists.
@@ -137,8 +126,7 @@
       icon_animation_changed_callback_lists_by_key_;
 
   // Mapping of keys to their associated progress ring animations.
-  std::map<const void*,
-           AnimationWithSubscription<HoldingSpaceProgressRingAnimation>>
+  std::map<const void*, std::unique_ptr<HoldingSpaceProgressRingAnimation>>
       ring_animations_by_key_;
 
   // Mapping of keys to their associated ring animation changed callback lists.
diff --git a/ash/system/time/calendar_event_list_view.cc b/ash/system/time/calendar_event_list_view.cc
index 98af72a..ee663b32 100644
--- a/ash/system/time/calendar_event_list_view.cc
+++ b/ash/system/time/calendar_event_list_view.cc
@@ -4,8 +4,11 @@
 
 #include "ash/system/time/calendar_event_list_view.h"
 
+#include "ash/public/cpp/system_tray_client.h"
+#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/time/calendar_utils.h"
 #include "ash/system/time/calendar_view_controller.h"
 #include "ash/system/tray/tray_popup_utils.h"
@@ -18,7 +21,7 @@
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/background.h"
-#include "ui/views/controls/label.h"
+#include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/vector_icons.h"
@@ -32,6 +35,39 @@
 
 }  // namespace
 
+// A view that's displayed when the user selects a day cell from the calendar
+// month view that has no events.  Clicking on it opens Google calendar.
+class CalendarEmptyEventListView : public views::LabelButton {
+ public:
+  explicit CalendarEmptyEventListView()
+      : views::LabelButton(
+            views::Button::PressedCallback(base::BindRepeating(
+                &CalendarEmptyEventListView::OpenCalendarDefault,
+                base::Unretained(this))),
+            l10n_util::GetStringUTF16(IDS_ASH_CALENDAR_NO_EVENTS)) {
+    SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
+  }
+  CalendarEmptyEventListView(const CalendarEmptyEventListView& other) = delete;
+  CalendarEmptyEventListView& operator=(
+      const CalendarEmptyEventListView& other) = delete;
+  ~CalendarEmptyEventListView() override = default;
+
+  // views::View:
+  void OnThemeChanged() override {
+    views::View::OnThemeChanged();
+    SetEnabledTextColors(calendar_utils::GetPrimaryTextColor());
+  }
+
+  // Callback that's invoked when the user clicks on "Open in Google calendar"
+  // in an empty event list.
+  void OpenCalendarDefault() {
+    GURL finalized_url;
+    bool opened_pwa = false;
+    Shell::Get()->system_tray_model()->client()->ShowCalendarEvent(
+        absl::nullopt, opened_pwa, finalized_url);
+  }
+};
+
 CalendarEventListView::CalendarEventListView(
     CalendarViewController* calendar_view_controller)
     : calendar_view_controller_(calendar_view_controller),
@@ -100,19 +136,30 @@
   std::list<google_apis::calendar::CalendarEvent> events =
       calendar_view_controller_->SelectedDateEvents();
 
-  // Sorts the event by start time.
-  events.sort([](google_apis::calendar::CalendarEvent& a,
-                 google_apis::calendar::CalendarEvent& b) {
-    return a.start_time().date_time() < b.start_time().date_time();
-  });
+  if (events.size() > 0) {
+    // Sorts the event by start time.
+    events.sort([](google_apis::calendar::CalendarEvent& a,
+                   google_apis::calendar::CalendarEvent& b) {
+      return a.start_time().date_time() < b.start_time().date_time();
+    });
 
-  for (const google_apis::calendar::CalendarEvent& event : events) {
-    auto* event_entry = content_view_->AddChildView(
-        std::make_unique<CalendarEventListItemView>(event));
+    for (const google_apis::calendar::CalendarEvent& event : events) {
+      auto* event_entry = content_view_->AddChildView(
+          std::make_unique<CalendarEventListItemView>(event));
 
-    // Needs to repaint the `content_view_`'s children.
-    event_entry->InvalidateLayout();
+      // Needs to repaint the `content_view_`'s children.
+      event_entry->InvalidateLayout();
+    }
+
+    return;
   }
+
+  // Show "Open in Google calendar"
+  CalendarEmptyEventListView* empty_list_view = content_view_->AddChildView(
+      std::make_unique<CalendarEmptyEventListView>());
+
+  // Needs to repaint the `content_view_`'s children.
+  empty_list_view->InvalidateLayout();
 }
 
 BEGIN_METADATA(CalendarEventListView, views::View);
diff --git a/ash/system/time/calendar_event_list_view_unittest.cc b/ash/system/time/calendar_event_list_view_unittest.cc
index 52e401b..24515086 100644
--- a/ash/system/time/calendar_event_list_view_unittest.cc
+++ b/ash/system/time/calendar_event_list_view_unittest.cc
@@ -91,7 +91,9 @@
 
   CreateEventListView(date);
 
-  EXPECT_EQ(0u, content_view()->children().size());
+  // No events, so we see the empty list default.
+  EXPECT_EQ(1u, content_view()->children().size());
+  EXPECT_EQ(u"Open in Google calendar", GetSummary(0)->GetText());
 
   SetSelectedDate(date);
 
@@ -110,7 +112,8 @@
   SetSelectedDate(date + base::Days(2));
 
   // 0 event on 20 Nov 2021.
-  EXPECT_EQ(0u, content_view()->children().size());
+  EXPECT_EQ(1u, content_view()->children().size());
+  EXPECT_EQ(u"Open in Google calendar", GetSummary(0)->GetText());
 
   SetSelectedDate(date + base::Days(3));
 
diff --git a/ash/system/time/calendar_view_unittest.cc b/ash/system/time/calendar_view_unittest.cc
index c425df2..57bde6b 100644
--- a/ash/system/time/calendar_view_unittest.cc
+++ b/ash/system/time/calendar_view_unittest.cc
@@ -5,6 +5,7 @@
 #include "ash/system/time/calendar_view.h"
 
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/style/icon_button.h"
 #include "ash/system/time/calendar_event_list_view.h"
 #include "ash/system/time/calendar_month_view.h"
@@ -17,6 +18,7 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/time/time_override.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/views/controls/button/label_button.h"
@@ -630,6 +632,12 @@
   PressTab();
   EXPECT_EQ(close_button(), focus_manager->GetFocusedView());
 
+  // Goes to empty list view.
+  PressTab();
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_CALENDAR_NO_EVENTS),
+            static_cast<views::LabelButton*>(focus_manager->GetFocusedView())
+                ->GetText());
+
   // Goes back to back button.
   PressTab();
 
diff --git a/ash/webui/personalization_app/BUILD.gn b/ash/webui/personalization_app/BUILD.gn
index dd37e13b..55900716 100644
--- a/ash/webui/personalization_app/BUILD.gn
+++ b/ash/webui/personalization_app/BUILD.gn
@@ -9,6 +9,7 @@
 
 static_library("personalization_app") {
   sources = [
+    "personalization_app_ambient_provider.h",
     "personalization_app_theme_provider.h",
     "personalization_app_ui.cc",
     "personalization_app_ui.h",
@@ -48,6 +49,8 @@
 source_set("browser_test_support") {
   testonly = true
   sources = [
+    "test/fake_personalization_app_ambient_provider.cc",
+    "test/fake_personalization_app_ambient_provider.h",
     "test/fake_personalization_app_theme_provider.cc",
     "test/fake_personalization_app_theme_provider.h",
     "test/fake_personalization_app_user_provider.cc",
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom
index 2b34b5c3..9763916 100644
--- a/ash/webui/personalization_app/mojom/personalization_app.mojom
+++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -280,3 +280,10 @@
   // Select the downloaded profile image as the user image.
   SelectProfileImage();
 };
+
+// Provides APIs to expose Ambient mode settings.
+// Used by the Personalization Hub.
+interface AmbientProvider{
+    // Boolean pref for whether ambient mode is enabled.
+    IsAmbientModeEnabled() => (bool enabled);
+};
diff --git a/ash/webui/personalization_app/personalization_app_ambient_provider.h b/ash/webui/personalization_app/personalization_app_ambient_provider.h
new file mode 100644
index 0000000..5fdf532
--- /dev/null
+++ b/ash/webui/personalization_app/personalization_app_ambient_provider.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
+#define ASH_WEBUI_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
+
+#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace ash {
+
+class PersonalizationAppAmbientProvider
+    : public personalization_app::mojom::AmbientProvider {
+ public:
+  virtual void BindInterface(
+      mojo::PendingReceiver<personalization_app::mojom::AmbientProvider>
+          receiver) = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index 0cad0fcd..2c09250 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/grit/ash_personalization_app_resources.h"
 #include "ash/grit/ash_personalization_app_resources_map.h"
+#include "ash/webui/personalization_app/personalization_app_ambient_provider.h"
 #include "ash/webui/personalization_app/personalization_app_theme_provider.h"
 #include "ash/webui/personalization_app/personalization_app_url_constants.h"
 #include "ash/webui/personalization_app/personalization_app_user_provider.h"
@@ -126,10 +127,12 @@
 
 PersonalizationAppUI::PersonalizationAppUI(
     content::WebUI* web_ui,
+    std::unique_ptr<PersonalizationAppAmbientProvider> ambient_provider,
     std::unique_ptr<PersonalizationAppThemeProvider> theme_provider,
     std::unique_ptr<PersonalizationAppUserProvider> user_provider,
     std::unique_ptr<PersonalizationAppWallpaperProvider> wallpaper_provider)
     : ui::MojoWebUIController(web_ui),
+      ambient_provider_(std::move(ambient_provider)),
       theme_provider_(std::move(theme_provider)),
       user_provider_(std::move(user_provider)),
       wallpaper_provider_(std::move(wallpaper_provider)) {
@@ -165,6 +168,12 @@
 PersonalizationAppUI::~PersonalizationAppUI() = default;
 
 void PersonalizationAppUI::BindInterface(
+    mojo::PendingReceiver<personalization_app::mojom::AmbientProvider>
+        receiver) {
+  ambient_provider_->BindInterface(std::move(receiver));
+}
+
+void PersonalizationAppUI::BindInterface(
     mojo::PendingReceiver<personalization_app::mojom::ThemeProvider> receiver) {
   theme_provider_->BindInterface(std::move(receiver));
 }
diff --git a/ash/webui/personalization_app/personalization_app_ui.h b/ash/webui/personalization_app/personalization_app_ui.h
index 5893744..e45a5b8 100644
--- a/ash/webui/personalization_app/personalization_app_ui.h
+++ b/ash/webui/personalization_app/personalization_app_ui.h
@@ -13,6 +13,7 @@
 
 namespace ash {
 
+class PersonalizationAppAmbientProvider;
 class PersonalizationAppThemeProvider;
 class PersonalizationAppWallpaperProvider;
 class PersonalizationAppUserProvider;
@@ -21,6 +22,7 @@
  public:
   PersonalizationAppUI(
       content::WebUI* web_ui,
+      std::unique_ptr<PersonalizationAppAmbientProvider> ambient_provider,
       std::unique_ptr<PersonalizationAppThemeProvider> theme_provider,
       std::unique_ptr<PersonalizationAppUserProvider> user_provider,
       std::unique_ptr<PersonalizationAppWallpaperProvider> wallpaper_provider);
@@ -31,6 +33,10 @@
   ~PersonalizationAppUI() override;
 
   void BindInterface(
+      mojo::PendingReceiver<personalization_app::mojom::AmbientProvider>
+          receiver);
+
+  void BindInterface(
       mojo::PendingReceiver<personalization_app::mojom::ThemeProvider>
           receiver);
 
@@ -42,6 +48,7 @@
           receiver);
 
  private:
+  std::unique_ptr<PersonalizationAppAmbientProvider> ambient_provider_;
   std::unique_ptr<PersonalizationAppThemeProvider> theme_provider_;
   std::unique_ptr<PersonalizationAppUserProvider> user_provider_;
   std::unique_ptr<PersonalizationAppWallpaperProvider> wallpaper_provider_;
diff --git a/ash/webui/personalization_app/resources/BUILD.gn b/ash/webui/personalization_app/resources/BUILD.gn
index b848c86..79a19de5 100644
--- a/ash/webui/personalization_app/resources/BUILD.gn
+++ b/ash/webui/personalization_app/resources/BUILD.gn
@@ -48,7 +48,7 @@
   "trusted/wallpaper/wallpaper_observer.ts",
   "trusted/wallpaper/wallpaper_reducers.ts",
   "trusted/wallpaper/wallpaper_state.ts",
-  "trusted/wallpaper/wallpaper_subpage.ts",
+  "trusted/wallpaper/index.ts",
 
   "untrusted/iframe_api.ts",
   "untrusted/setup.ts",
@@ -83,6 +83,7 @@
   "trusted/wallpaper/wallpaper_images_element.ts",
   "trusted/wallpaper/wallpaper_preview_element.ts",
   "trusted/wallpaper/wallpaper_selected_element.ts",
+  "trusted/wallpaper/wallpaper_subpage_element.ts",
 
   "untrusted/collections_grid.ts",
   "untrusted/images_grid.ts",
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_app.ts b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
index 8ed58e5..16fa214 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_app.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
@@ -21,7 +21,7 @@
 import './user_preview_element.js';
 import './user/avatar_list_element.js';
 import './user/user_subpage_element.js';
-import './wallpaper/wallpaper_subpage.js';
+import './wallpaper/index.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_router_element.html b/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
index 151571f..fc2960e 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
+++ b/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
@@ -18,34 +18,6 @@
   personalization-breadcrumb {
     grid-area: breadcrumb;
   }
-  #wallpaperContainer {
-    display: grid;
-    flex-grow: 1;
-    grid-template-areas:
-      '. leftspacer selected   rightspacer .'
-      '. leftspacer .          rightspacer .'
-      '. leftspacer imagegrid  rightspacer .'
-      '. leftspacer .          rightspacer .';
-    grid-template-columns: 1fr 16px minmax(568px, 920px) 16px 1fr;
-    grid-template-rows: 172px 12px 1fr 12px;
-    position: relative;
-    width: 100%;
-  }
-  #leftspacer {
-    grid-area: leftspacer;
-  }
-  #rightspacer {
-    grid-area: rightspacer;
-  }
-  wallpaper-selected {
-    grid-area: selected;
-  }
-  wallpaper-collections,
-  wallpaper-images,
-  google-photos-collection,
-  local-images {
-    grid-area: imagegrid;
-  }
   personalization-toast {
     bottom: 16px;
     left: 16px;
@@ -60,6 +32,9 @@
     position: absolute;
     width: 100%;
   }
+  wallpaper-subpage {
+    flex-grow: 1;
+  }
 </style>
 <div id="container">
   <!-- dwell-time is set to 200ms to populate history state more quickly while
@@ -87,30 +62,8 @@
     <user-subpage></user-subpage>
   </template>
   <template is="dom-if" if="[[shouldShowWallpaperSubpage_(path_)]]">
-    <div id="wallpaperContainer">
-      <!-- Prevent left margin from collapsing on narrow window in RTL -->
-      <div id="leftspacer"></div>
-      <wallpaper-selected path="[[path_]]" collection-id="[[queryParams_.id]]">
-      </wallpaper-selected>
-      <!-- do not use hidden$ here - need to listen on property change in
-          these elements. -->
-      <wallpaper-collections hidden="[[!shouldShowCollections_(path_)]]">
-      </wallpaper-collections>
-      <wallpaper-images collection-id="[[queryParams_.id]]"
-        hidden="[[!shouldShowCollectionImages_(path_)]]"></wallpaper-images>
-      <template is="dom-if" if="[[isGooglePhotosIntegrationEnabled_()]]">
-        <google-photos-collection
-          album-id="[[queryParams_.googlePhotosAlbumId]]"
-          hidden="[[!shouldShowGooglePhotosCollection_(path_)]]">
-        </google-photos-collection>
-      </template>
-      <local-images hidden="[[!shouldShowLocalCollection_(path_)]]">
-      </local-images>
-      <!-- Prevent the right margin from collapsing when window gets very
-           narrow -->
-      <div id="rightspacer"></div>
-      <wallpaper-fullscreen></wallpaper-fullscreen>
-    </div>
+    <wallpaper-subpage path="[[path_]]" query-params="[[queryParams_]]">
+    </wallpaper-subpage>
   </template>
   <personalization-toast></personalization-toast>
 </div>
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
index 4b0386d..9581ac4 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
@@ -104,39 +104,16 @@
     this.setProperties({path_: path, queryParams_: queryParams});
   }
 
-  private shouldShowCollections_(path: string): boolean {
-    return path === Paths.Collections;
-  }
-
-  private shouldShowCollectionImages_(path: string): boolean {
-    return path === Paths.CollectionImages;
-  }
-
-  private shouldShowGooglePhotosCollection_(path: string): boolean {
-    return path === Paths.GooglePhotosCollection;
-  }
-
-  private shouldShowLocalCollection_(path: string): boolean {
-    return path === Paths.LocalCollection;
-  }
-
-  /**
-   * Whether Google Photos integration is enabled.
-   */
-  private isGooglePhotosIntegrationEnabled_(): boolean {
-    return loadTimeData.getBoolean('isGooglePhotosIntegrationEnabled');
-  }
-
   private shouldShowRootPage_(path: string|null): boolean {
     return isPersonalizationHubEnabled() && path === Paths.Root;
   }
 
   private shouldShowAmbientSubpage_(path: string|null): boolean {
-    return !!path?.startsWith(Paths.Ambient);
+    return isPersonalizationHubEnabled() && !!path?.startsWith(Paths.Ambient);
   }
 
   private shouldShowUserSubpage_(path: string|null): boolean {
-    return !!path?.startsWith(Paths.User);
+    return isPersonalizationHubEnabled() && !!path?.startsWith(Paths.User);
   }
 
   private shouldShowWallpaperSubpage_(path: string|null): boolean {
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
similarity index 96%
rename from ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage.ts
rename to ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
index 1ba3a917..2c679cf 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
@@ -15,6 +15,7 @@
 import './wallpaper_images_element.js';
 import './wallpaper_preview_element.js';
 import './wallpaper_selected_element.js';
+import './wallpaper_subpage_element.js';
 import './styles.js';
 
 import {onMessageReceived} from './untrusted_message_handler.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html
new file mode 100644
index 0000000..b94a8d0
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html
@@ -0,0 +1,57 @@
+<style>
+  #wallpaperContainer {
+    display: grid;
+    grid-template-areas:
+      '. leftspacer selected   rightspacer .'
+      '. leftspacer .          rightspacer .'
+      '. leftspacer imagegrid  rightspacer .'
+      '. leftspacer .          rightspacer .';
+    grid-template-columns: 1fr 16px minmax(568px, 920px) 16px 1fr;
+    grid-template-rows: 172px 12px 1fr 12px;
+    height: 100%;
+    position: relative;
+    width: 100%;
+  }
+
+  #leftspacer {
+    grid-area: leftspacer;
+  }
+
+  #rightspacer {
+    grid-area: rightspacer;
+  }
+
+  wallpaper-selected {
+    grid-area: selected;
+  }
+
+  wallpaper-collections,
+  wallpaper-images,
+  google-photos-collection,
+  local-images {
+    grid-area: imagegrid;
+  }
+</style>
+<div id="wallpaperContainer">
+  <!-- Prevent left margin from collapsing on narrow window in RTL -->
+  <div id="leftspacer"></div>
+  <wallpaper-selected path="[[path]]" collection-id="[[queryParams.id]]">
+  </wallpaper-selected>
+  <!-- do not use hidden$ here - need to listen on property change in
+          these elements. -->
+  <wallpaper-collections hidden="[[!shouldShowCollections_(path)]]">
+  </wallpaper-collections>
+  <wallpaper-images collection-id="[[queryParams.id]]"
+      hidden="[[!shouldShowCollectionImages_(path)]]"></wallpaper-images>
+  <template is="dom-if" if="[[isGooglePhotosIntegrationEnabled_()]]">
+    <google-photos-collection album-id="[[queryParams.googlePhotosAlbumId]]"
+        hidden="[[!shouldShowGooglePhotosCollection_(path)]]">
+    </google-photos-collection>
+  </template>
+  <local-images hidden="[[!shouldShowLocalCollection_(path)]]">
+  </local-images>
+  <!-- Prevent the right margin from collapsing when window gets very
+           narrow -->
+  <div id="rightspacer"></div>
+  <wallpaper-fullscreen></wallpaper-fullscreen>
+</div>
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts
new file mode 100644
index 0000000..7fc9275
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts
@@ -0,0 +1,56 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This component displays the wallpaper section of the
+ * personalization SWA.
+ */
+
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Paths} from '../personalization_router_element.js';
+
+export class WallpaperSubpage extends PolymerElement {
+  static get is() {
+    return 'wallpaper-subpage';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {path: String, queryParams: Object};
+  }
+
+  path: string;
+  queryParams: Record<string, string>;
+
+  private shouldShowCollections_(path: string): boolean {
+    return path === Paths.Collections;
+  }
+
+  private shouldShowCollectionImages_(path: string): boolean {
+    return path === Paths.CollectionImages;
+  }
+
+  private shouldShowGooglePhotosCollection_(path: string): boolean {
+    return this.isGooglePhotosIntegrationEnabled_() &&
+        path === Paths.GooglePhotosCollection;
+  }
+
+  private shouldShowLocalCollection_(path: string): boolean {
+    return path === Paths.LocalCollection;
+  }
+
+  /**
+   * Whether Google Photos integration is enabled.
+   */
+  private isGooglePhotosIntegrationEnabled_(): boolean {
+    return loadTimeData.getBoolean('isGooglePhotosIntegrationEnabled');
+  }
+}
+
+customElements.define(WallpaperSubpage.is, WallpaperSubpage);
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.cc b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.cc
new file mode 100644
index 0000000..33cd764
--- /dev/null
+++ b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium 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/webui/personalization_app/test/fake_personalization_app_ambient_provider.h"
+
+#include "ash/webui/personalization_app/mojom/personalization_app.mojom-forward.h"
+#include "content/public/browser/web_ui.h"
+
+namespace ash {
+
+FakePersonalizationAppAmbientProvider::FakePersonalizationAppAmbientProvider(
+    content::WebUI* web_ui) {}
+
+FakePersonalizationAppAmbientProvider::
+    ~FakePersonalizationAppAmbientProvider() = default;
+
+void FakePersonalizationAppAmbientProvider::BindInterface(
+    mojo::PendingReceiver<personalization_app::mojom::AmbientProvider>
+        receiver) {
+  ambient_receiver_.reset();
+  ambient_receiver_.Bind(std::move(receiver));
+}
+
+void FakePersonalizationAppAmbientProvider::IsAmbientModeEnabled(
+    IsAmbientModeEnabledCallback callback) {
+  std::move(callback).Run(std::move(true));
+}
+
+}  // namespace ash
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h
new file mode 100644
index 0000000..2ba6f52
--- /dev/null
+++ b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h
@@ -0,0 +1,49 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_PERSONALIZATION_APP_TEST_FAKE_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
+#define ASH_WEBUI_PERSONALIZATION_APP_TEST_FAKE_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
+
+#include "ash/webui/personalization_app/personalization_app_ambient_provider.h"
+
+#include <stdint.h>
+
+#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace content {
+class WebUI;
+}  // namespace content
+
+namespace ash {
+
+class FakePersonalizationAppAmbientProvider
+    : public PersonalizationAppAmbientProvider {
+ public:
+  explicit FakePersonalizationAppAmbientProvider(content::WebUI* web_ui);
+
+  FakePersonalizationAppAmbientProvider(
+      const FakePersonalizationAppAmbientProvider&) = delete;
+  FakePersonalizationAppAmbientProvider& operator=(
+      const FakePersonalizationAppAmbientProvider&) = delete;
+
+  ~FakePersonalizationAppAmbientProvider() override;
+
+  // PersonalizationAppAmbientProvider:
+  void BindInterface(
+      mojo::PendingReceiver<personalization_app::mojom::AmbientProvider>
+          receiver) override;
+
+  // personalization_app::mojom::AmbientProvider
+  void IsAmbientModeEnabled(IsAmbientModeEnabledCallback callback) override;
+
+ private:
+  mojo::Receiver<ash::personalization_app::mojom::AmbientProvider>
+      ambient_receiver_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_PERSONALIZATION_APP_TEST_FAKE_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
diff --git a/ash/webui/personalization_app/test/personalization_app_browsertest.js b/ash/webui/personalization_app/test/personalization_app_browsertest.js
index bc36ef7..ef76aac5 100644
--- a/ash/webui/personalization_app/test/personalization_app_browsertest.js
+++ b/ash/webui/personalization_app/test/personalization_app_browsertest.js
@@ -150,13 +150,22 @@
 
 TEST_F('WallpaperSubpageBrowserTest', 'LoadsCollectionsUntrustedIframe', () => {
   const router = document.querySelector('personalization-router');
-  assertTrue(!!router);
+  assertTrue(!!router, 'personalization-router should be top level element');
 
-  const collections = router.shadowRoot.querySelector('wallpaper-collections');
-  assertTrue(!!collections);
+  const wallpaperSubpage = router.shadowRoot.querySelector('wallpaper-subpage');
+  assertTrue(
+      !!wallpaperSubpage,
+      'wallpaper-subpage should be found under personalization-router');
+
+  const collections =
+      wallpaperSubpage.shadowRoot.querySelector('wallpaper-collections');
+  assertTrue(
+      !!collections,
+      'wallpaper-collections should be found under wallpaper-subpage');
+
 
   const iframe = collections.shadowRoot.getElementById('collections-iframe');
-  assertTrue(!!iframe);
+  assertTrue(!!iframe, 'iframe with id collections-iframe should be visible');
 
   assertEquals(
       'chrome-untrusted://personalization/untrusted/collections.html',
diff --git a/ash/webui/personalization_app/test/personalization_app_browsertest_fixture.cc b/ash/webui/personalization_app/test/personalization_app_browsertest_fixture.cc
index c0daa36..8925bcf 100644
--- a/ash/webui/personalization_app/test/personalization_app_browsertest_fixture.cc
+++ b/ash/webui/personalization_app/test/personalization_app_browsertest_fixture.cc
@@ -8,6 +8,7 @@
 
 #include "ash/webui/personalization_app/personalization_app_ui.h"
 #include "ash/webui/personalization_app/personalization_app_url_constants.h"
+#include "ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h"
 #include "ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h"
 #include "ash/webui/personalization_app/test/fake_personalization_app_user_provider.h"
 #include "ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.h"
@@ -16,6 +17,8 @@
 std::unique_ptr<content::WebUIController>
 TestPersonalizationAppWebUIProvider::NewWebUI(content::WebUI* web_ui,
                                               const GURL& url) {
+  auto ambient_provider =
+      std::make_unique<ash::FakePersonalizationAppAmbientProvider>(web_ui);
   auto theme_provider =
       std::make_unique<FakePersonalizationAppThemeProvider>(web_ui);
   auto wallpaper_provider =
@@ -23,8 +26,8 @@
   auto user_provider =
       std::make_unique<ash::FakePersonalizationAppUserProvider>(web_ui);
   return std::make_unique<ash::PersonalizationAppUI>(
-      web_ui, std::move(theme_provider), std::move(user_provider),
-      std::move(wallpaper_provider));
+      web_ui, std::move(ambient_provider), std::move(theme_provider),
+      std::move(user_provider), std::move(wallpaper_provider));
 }
 
 void PersonalizationAppBrowserTestFixture::SetUpOnMainThread() {
diff --git a/ash/webui/shimless_rma/resources/BUILD.gn b/ash/webui/shimless_rma/resources/BUILD.gn
index 0c87633..8a6907f 100644
--- a/ash/webui/shimless_rma/resources/BUILD.gn
+++ b/ash/webui/shimless_rma/resources/BUILD.gn
@@ -37,6 +37,7 @@
   "reimaging_provisioning_page.js",
   "repair_component_chip.js",
   "shimless_rma.js",
+  "shimless_rma_fonts_css.js",
   "shimless_rma_shared_css.js",
   "splash_screen.js",
   "wrapup_finalize_page.js",
diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.html b/ash/webui/shimless_rma/resources/calibration_component_chip.html
index 17e686cf..9207f6f 100644
--- a/ash/webui/shimless_rma/resources/calibration_component_chip.html
+++ b/ash/webui/shimless_rma/resources/calibration_component_chip.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style shimless-rma-shared">
+<style include="cr-shared-style shimless-rma-shared shimless-fonts">
   :host {
     padding: 1px;
   }
@@ -12,7 +12,12 @@
     align-items: normal;
     border-radius: 4px;
     box-shadow: var(--cr-card-shadow);
+    color: var(--shimless-component-text-color);
+    font-family: var(--shimless-component-font-family);
+    font-size: var(--shimless-component-font-size);
+    font-weight: var(--shimless-medium-font-weight);
     height: 70px;
+    line-height: var(--shimless-component-line-height);
     margin-bottom: 20px;
     margin-inline-end: 10px;
     width: 190px;
diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.js b/ash/webui/shimless_rma/resources/calibration_component_chip.js
index 62d71e7c..2e0e8ee 100644
--- a/ash/webui/shimless_rma/resources/calibration_component_chip.js
+++ b/ash/webui/shimless_rma/resources/calibration_component_chip.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './shimless_rma_fonts_css.js';
 import './shimless_rma_shared_css.js';
 import './icons.js';
 
diff --git a/ash/webui/shimless_rma/resources/critical_error_page.html b/ash/webui/shimless_rma/resources/critical_error_page.html
index 37d77eb..3862c23 100644
--- a/ash/webui/shimless_rma/resources/critical_error_page.html
+++ b/ash/webui/shimless_rma/resources/critical_error_page.html
@@ -25,7 +25,7 @@
         class="small-icon">
     </iron-icon>
     <h1>[[i18n('criticalErrorTitleText')]]</h1>
-    <div>[[i18n('criticalErrorMessageText')]]</div>
+    <div class="instructions">[[i18n('criticalErrorMessageText')]]</div>
     <div id="writeProtectStatus">[[statusString_]]</div>
     <cr-button id="exitToLoginButton" on-click="onExitToLoginButtonClicked_"
         disabled="[[allButtonsDisabled]]">
diff --git a/ash/webui/shimless_rma/resources/index.html b/ash/webui/shimless_rma/resources/index.html
index 52597ab3..43efdcb 100644
--- a/ash/webui/shimless_rma/resources/index.html
+++ b/ash/webui/shimless_rma/resources/index.html
@@ -11,6 +11,7 @@
     <shimless-rma></shimless-rma>
 
     <link rel="stylesheet" src="shimless_rma_shared_css.js">
+    <link rel="stylesheet" src="shimless_rma_fonts_css.js">
     <script type="module" src="shimless_rma.js"></script>
     <!-- Below mojo script required to run browser tests -->
     <script src="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js">
diff --git a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html
index f6a165ec..60b91572 100644
--- a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style shimless-rma-shared">
+<style include="cr-shared-style shimless-rma-shared shimless-fonts">
   #dialogBody {
     overflow-wrap: anywhere;
   }
@@ -8,7 +8,11 @@
   }
 
   #inputValidationLabel {
-    color: var(--google-grey-600);
+    color: var(--shimless-hint-text-color);
+    font-family: var(--shimless-hint-font-family);
+    font-size: var(--shimless-hint-font-size);
+    font-weight: var(--shimless-medium-font-weight);
+    line-height: var(--shimless-hint-line-height);
   }
 
   :host([rsu-code-invalid_]) #inputValidationLabel {
@@ -22,15 +26,24 @@
   #inputContainer {
     width: 275px;
   }
+
+  #rsuCodeDialogLink {
+    color: var(--cros-link-color);
+    text-decoration: none;
+  }
 </style>
 
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('rsuCodePageTitleText')]]</h1>
-    <div>
+    <div class="instructions">
       <span inner-h-t-m-l="[[rsuInstructionsText_]]"></span>
     </div>
     <div id="inputContainer">
+      <div id="inputValidationLabel">
+        [[i18n('rsuCodeLabelText')]]
+        <span id="rsuCodeLengthLabel">[[rsuCodeLengthLabel_]]</span>
+      </div>
       <cr-input
           id="rsuCode"
           value="{{rsuCode_}}"
@@ -39,10 +52,6 @@
           auto-validate
           disabled="[[allButtonsDisabled]]">
       </cr-input>
-      <div id="inputValidationLabel">
-        [[i18n('rsuCodeLabelText')]]
-        <span id="rsuCodeLengthLabel">[[rsuCodeLengthLabel_]]</span>
-      </div>
     </div>
   </div>
   <div slot="body">
diff --git a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
index a2cbdc66..73bbb84 100644
--- a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './shimless_rma_fonts_css.js';
 import './shimless_rma_shared_css.js';
 import './base_page.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
diff --git a/ash/webui/shimless_rma/resources/onboarding_landing_page.html b/ash/webui/shimless_rma/resources/onboarding_landing_page.html
index 35e3c437..ec5f143 100644
--- a/ash/webui/shimless_rma/resources/onboarding_landing_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_landing_page.html
@@ -8,7 +8,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('welcomeTitleText')]]</h1>
-    <div>[[i18n('beginRmaWarningText')]]</div>
+    <div class="instructions">[[i18n('beginRmaWarningText')]]</div>
     <div id="verificationMessage">
       <paper-spinner-lite id="busyIcon" hidden$="[[!verificationInProgress_]]"
         active>
@@ -17,15 +17,15 @@
         icon="[[getVerificationIcon_(isCompliant_)]]"
         hidden$="[[verificationInProgress_]]" class="small-icon">
       </iron-icon>
-      <span hidden$="[[!verificationInProgress_]]">
+      <span hidden$="[[!verificationInProgress_]]" class="instructions">
         [[i18n('validatingComponentsText')]]
       </span>
       <span hidden$="[[verificationInProgress_]]">
-        <span hidden$="[[!isCompliant_]]">
+        <span hidden$="[[!isCompliant_]]" class="instructions">
           [[i18n('validatedComponentsSuccessText')]]
         </span>
         <span inner-h-t-m-l="[[verificationFailedMessage_]]"
-            hidden$="[[isCompliant_]]">
+            hidden$="[[isCompliant_]]" class="instructions">
         </span>
       </span>
     </div>
diff --git a/ash/webui/shimless_rma/resources/onboarding_network_page.html b/ash/webui/shimless_rma/resources/onboarding_network_page.html
index e955619..5361ada 100644
--- a/ash/webui/shimless_rma/resources/onboarding_network_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_network_page.html
@@ -39,7 +39,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('connectNetworkTitleText')]]</h1>
-    <div>[[i18n('connectNetworkDescriptionText')]]</div>
+    <div class="instructions">[[i18n('connectNetworkDescriptionText')]]</div>
   </div>
   <div slot="body" id="container">
     <network-list id="networkList" show-technology-badge
diff --git a/ash/webui/shimless_rma/resources/onboarding_select_components_page.html b/ash/webui/shimless_rma/resources/onboarding_select_components_page.html
index 193dfe1a..c42bd3e 100644
--- a/ash/webui/shimless_rma/resources/onboarding_select_components_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_select_components_page.html
@@ -1,10 +1,14 @@
 <style include="cr-shared-style shimless-rma-shared">
+  #reworkFlowLink {
+    color: var(--cros-link-color);
+    text-decoration: none;
+  }
 </style>
 
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('selectComponentsTitleText')]]</h1>
-    <div>
+    <div class="instructions">
       <span inner-h-t-m-l="[[reworkFlowLinkText_]]"></span>
     </div>
   </div>
diff --git a/ash/webui/shimless_rma/resources/onboarding_update_page.html b/ash/webui/shimless_rma/resources/onboarding_update_page.html
index e1891cc1..7fe653b 100644
--- a/ash/webui/shimless_rma/resources/onboarding_update_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_update_page.html
@@ -13,21 +13,23 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('osUpdateTitleText')]]</h1>
-    <div hidden$="[[isCompliant_]]">
+    <div hidden$="[[isCompliant_]]" class="instructions">
       <div inner-h-t-m-l="[[verificationFailedMessage_]]"></div>
       <div>[[i18n('osUpdateUnqualifiedComponentsBottomText')]]</div>
     </div>
-    <div hidden$="[[!isCompliant_]]">[[updateNoticeMessage_]]</div>
+    <div hidden$="[[!isCompliant_]]" class="instructions">
+      [[updateNoticeMessage_]]
+    </div>
     <div id="versionInfo">
       <iron-icon id="updateIcon" class="small-icon"
           icon="[[getUpdateNoticeIcon_(updateAvailable_)]]">
       </iron-icon>
-      [[currentVersionText_]]
+      <div class="instructions">[[currentVersionText_]]</div>
     </div>
     <div id="progressMessage">
       <paper-spinner-lite hidden$="[[!updateInProgress_]]" active>
       </paper-spinner-lite>
-      [[updateProgressMessage_]]
+      <div class="instructions">[[updateProgressMessage_]]</div>
     </div>
     <p id="networkUnavailable" hidden$="[[networkAvailable]]">
       [[i18n('onboardingUpdateConnectToInternet')]]
diff --git a/ash/webui/shimless_rma/resources/onboarding_wait_for_manual_wp_disable_page.html b/ash/webui/shimless_rma/resources/onboarding_wait_for_manual_wp_disable_page.html
index 3a0d745b..2f0c548 100644
--- a/ash/webui/shimless_rma/resources/onboarding_wait_for_manual_wp_disable_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_wait_for_manual_wp_disable_page.html
@@ -4,7 +4,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('manuallyDisableWpTitleText')]]</h1>
-    <div id="manuallyDisableHwwpInstructions">
+    <div id="manuallyDisableHwwpInstructions" class="instructions">
       [[i18n('manuallyDisableWpInstructionsText')]]
     </div>
   </div>
diff --git a/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.html b/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.html
index a292935..7e2d5b0 100644
--- a/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.html
@@ -4,7 +4,9 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('wpDisableCompletePageTitleText')]]</h1>
-    <div id="writeProtectAction">[[actionString_]]</div>
+    <div id="writeProtectAction" class="instructions">
+      [[actionString_]]
+    </div>
   </div>
   <div slot="body">
     <!-- TODO(gavindodd): Replace with correct graphic -->
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
index b0a96887..36d2901 100644
--- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
@@ -4,7 +4,9 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('calibrationFailedTitleText')]]</h1>
-    <div>[[i18n('calibrationFailedInstructionsText')]]</div>
+    <div class="instructions">
+      [[i18n('calibrationFailedInstructionsText')]]
+    </div>
     <div>
       <cr-button id="retryCalibrationButton" class="cancel-button"
           on-click="onRetryCalibrationButtonClicked_"
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_run_page.html b/ash/webui/shimless_rma/resources/reimaging_calibration_run_page.html
index 9bd369be..9c72288 100644
--- a/ash/webui/shimless_rma/resources/reimaging_calibration_run_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_calibration_run_page.html
@@ -4,7 +4,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('runCalibrationTitleText')]]</h1>
-    <div id="calibration">
+    <div id="calibration" class="instructions">
       <span>[[calibrationStatusMessage_]]</span>
     </div>
   </div>
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_setup_page.html b/ash/webui/shimless_rma/resources/reimaging_calibration_setup_page.html
index 9c2a0ef9..c94d5d8 100644
--- a/ash/webui/shimless_rma/resources/reimaging_calibration_setup_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_calibration_setup_page.html
@@ -4,7 +4,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('setupCalibrationTitleText')]]</h1>
-    <div id="instructions">
+    <div id="instructions" class="instructions">
       <span>[[calibrationInstructions_]]</span>
     </div>
   </div>
diff --git a/ash/webui/shimless_rma/resources/reimaging_device_information_page.html b/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
index a2e92f1..1b4ea54 100644
--- a/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style shimless-rma-shared">
+<style include="cr-shared-style shimless-rma-shared shimless-fonts">
   .input-row {
     margin-bottom: 30px;
   }
@@ -13,8 +13,12 @@
   }
 
   .sku-warning {
-    color: gray; /* TODO(joonbug): Update cros colors */
+    color: var(--shimless-warning-text-color);
     display: flex;
+    font-family: var(--shimless-warning-font-family);
+    font-size: var(--shimless-warning-font-size);
+    font-weight: var(--shimless-regular-font-weight);
+    line-height: var(--shimless-warning-line-height);
     max-width: 400px;
   }
 
@@ -31,12 +35,22 @@
   select {
     margin-inline-end: 20px;
   }
+
+  .cr-form-field-label {
+    color: var(--shimless-hint-text-color);
+    font-family: var(--shimless-hint-font-family);
+    font-size: var(--shimless-hint-font-size);
+    font-weight: var(--shimless-medium-font-weight);
+    line-height: var(--shimless-hint-line-height);
+  }
 </style>
 
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('confirmDeviceInfoTitle')]]</h1>
-    <div>[[i18n('confirmDeviceInfoInstructions')]]</div>
+    <div class="instructions">
+      [[i18n('confirmDeviceInfoInstructions')]]
+    </div>
   </div>
   <div slot="body">
     <div class="input-row">
diff --git a/ash/webui/shimless_rma/resources/reimaging_device_information_page.js b/ash/webui/shimless_rma/resources/reimaging_device_information_page.js
index b182db36..24e00c90 100644
--- a/ash/webui/shimless_rma/resources/reimaging_device_information_page.js
+++ b/ash/webui/shimless_rma/resources/reimaging_device_information_page.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './shimless_rma_fonts_css.js';
 import './shimless_rma_shared_css.js';
 import './base_page.js';
 import './icons.js';
diff --git a/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.html b/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.html
index 7415790..b21b195 100644
--- a/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.html
@@ -4,7 +4,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('firmwareUpdatePageTitleText')]]</h1>
-    <div id="firmwareUpdateStatus">[[statusString_]]</div>
+    <div id="firmwareUpdateStatus" class="instructions">[[statusString_]]</div>
   </div>
   <div slot="body">
     <!-- TODO(gavindodd): Replace with spinner -->
diff --git a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
index 1bc5762..7a5232e 100644
--- a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
@@ -4,7 +4,9 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('provisioningPageTitleText')]]</h1>
-    <div id="provisioningDeviceStatus">[[statusString_]]</div>
+    <div id="provisioningDeviceStatus" class="instructions">
+      [[statusString_]]
+    </div>
     <div>
       <cr-button id="retryProvisioningButton" class="cancel-button"
           on-click="onRetryProvsioningButtonClicked_"
diff --git a/ash/webui/shimless_rma/resources/repair_component_chip.html b/ash/webui/shimless_rma/resources/repair_component_chip.html
index 463d60e..fc2e078 100644
--- a/ash/webui/shimless_rma/resources/repair_component_chip.html
+++ b/ash/webui/shimless_rma/resources/repair_component_chip.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style shimless-rma-shared">
+<style include="cr-shared-style shimless-rma-shared shimless-fonts">
   :host {
     padding: 1px;
   }
@@ -12,7 +12,12 @@
     align-items: normal;
     border-radius: 4px;
     box-shadow: var(--cr-card-shadow);
+    color: var(--shimless-component-text-color);
+    font-family: var(--shimless-component-font-family);
+    font-size: var(--shimless-component-font-size);
+    font-weight: var(--shimless-medium-font-weight);
     height: 70px;
+    line-height: var(--shimless-component-line-height);
     margin-bottom: 20px;
     margin-inline-end: 10px;
     width: 190px;
diff --git a/ash/webui/shimless_rma/resources/repair_component_chip.js b/ash/webui/shimless_rma/resources/repair_component_chip.js
index 858527e..54c4e13 100644
--- a/ash/webui/shimless_rma/resources/repair_component_chip.js
+++ b/ash/webui/shimless_rma/resources/repair_component_chip.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './shimless_rma_fonts_css.js';
 import './shimless_rma_shared_css.js';
 import './icons.js';
 
diff --git a/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.html b/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.html
new file mode 100644
index 0000000..43fabbd
--- /dev/null
+++ b/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.html
@@ -0,0 +1,39 @@
+<template>
+  <style>
+    :host {
+      --shimless-title-font-family: "Google Sans", Roboto, sans-serif;
+      --shimless-instructions-font-family: Roboto, sans-serif;
+      --shimless-component-font-family: Roboto, sans-serif;
+      --shimless-hint-font-family: Roboto, sans-serif;
+      --shimless-warning-font-family: Roboto, sans-serif;
+      --shimless-button-label-font-family: "Google Sans", Roboto, sans-serif;
+      --shimless-input-font-family: Roboto, sans-serif;
+
+      --shimless-title-font-size: 28px;
+      --shimless-component-font-size: 14px;
+      --shimless-instructions-font-size: 14px;
+      --shimless-hint-font-size: 10px;
+      --shimless-warning-font-size: 12px;
+      --shimless-button-label-font-size: 16px;
+      --shimless-input-font-size: 13px;
+
+      --shimless-title-line-height: 36px;
+      --shimless-instructions-line-height: 20px;
+      --shimless-component-line-height: 20px;
+      --shimless-hint-line-height: 10px;
+      --shimless-warning-line-height: 18px;
+      --shimless-button-label-line-height: 24px;
+      --shimless-input-line-height: 20px;
+
+      --shimless-regular-font-weight: 400;
+      --shimless-medium-font-weight: 500;
+
+      --shimless-title-text-color: var(--cros-text-color-primary);
+      --shimless-instructions-text-color: var(--cros-text-color-secondary);
+      --shimless-component-text-color: var(--cros-text-color-secondary);
+      --shimless-hint-text-color: var(--google-grey-600);
+      --shimless-warning-text-color: var(--cros-text-color-secondary);
+      --shimless-button-label-text-color: var(--cros-text-color-primary);
+    }
+  </style>
+</template>
\ No newline at end of file
diff --git a/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.js b/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.js
new file mode 100644
index 0000000..5bf20ea2
--- /dev/null
+++ b/ash/webui/shimless_rma/resources/shimless_rma_fonts_css.js
@@ -0,0 +1,11 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const template = document.createElement('template');
+template.innerHTML = `
+<dom-module id="shimless-fonts">{__html_template__}</dom-module>
+`;
+document.body.appendChild(template.content.cloneNode(true));
\ No newline at end of file
diff --git a/ash/webui/shimless_rma/resources/shimless_rma_shared_css.html b/ash/webui/shimless_rma/resources/shimless_rma_shared_css.html
index 8c7b038..9b6a260 100644
--- a/ash/webui/shimless_rma/resources/shimless_rma_shared_css.html
+++ b/ash/webui/shimless_rma/resources/shimless_rma_shared_css.html
@@ -5,13 +5,13 @@
 <link rel="stylesheet" href="chrome://resources/cr_elements/md_select_css.html">
 
 <template>
-  <style include="cr-shared-style md-select">
+  <style include="cr-shared-style md-select shimless-fonts">
     html {
       background-color: var(--cros-bg-color);
     }
 
     .indented-description {
-      color: gray; /* TODO(joonbug): update cros color */
+      color: var(--shimless-instructions-text-color);
       padding: 0 0 0 48px; /* specify all to override */
     }
 
@@ -24,5 +24,28 @@
       height: 300px;
       width: 300px;
     }
+
+    h1 {
+      color: var(--shimless-title-text-color);
+      font-family: var(--shimless-title-font-family);
+      font-size: var(--shimless-title-font-size);
+      font-weight: var(--shimless-medium-font-weight);
+      line-height: var(--shimless-title-line-height);
+    }
+
+    .instructions {
+      color: var(--shimless-instructions-text-color);
+      font-family: var(--shimless-instructions-font-family);
+      font-size: var(--shimless-instructions-font-size);
+      font-weight: var(--shimless-regular-font-weight);
+      line-height: var(--shimless-instructions-line-height);
+    }
+
+    cr-input {
+      font-family: var(--shimless-input-font-family);
+      font-size: var(--shimless-input-font-size);
+      font-weight: var(--shimless-regular-font-weight);
+      line-height: var(--shimless-input-line-height);
+    }
   </style>
 </template>
diff --git a/ash/webui/shimless_rma/resources/shimless_rma_shared_css.js b/ash/webui/shimless_rma/resources/shimless_rma_shared_css.js
index 86d1a0d..cb39fdd 100644
--- a/ash/webui/shimless_rma/resources/shimless_rma_shared_css.js
+++ b/ash/webui/shimless_rma/resources/shimless_rma_shared_css.js
@@ -5,6 +5,7 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import './shimless_rma_fonts_css.js';
 
 const template = document.createElement('template');
 template.innerHTML = `
diff --git a/ash/webui/shimless_rma/resources/wrapup_finalize_page.html b/ash/webui/shimless_rma/resources/wrapup_finalize_page.html
index 5156b10..0a58719 100644
--- a/ash/webui/shimless_rma/resources/wrapup_finalize_page.html
+++ b/ash/webui/shimless_rma/resources/wrapup_finalize_page.html
@@ -4,7 +4,9 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('finalizePageTitleText')]]</h1>
-    <div id="finalizationMessage">[[finalizationMessage_]]</div>
+    <div id="finalizationMessage" class="instructions">
+      [[finalizationMessage_]]
+    </div>
     <div>
       <cr-button id="retryFinalizationButton" class="cancel-button"
           on-click="onRetryFinalizationButtonClicked_"
diff --git a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
index f4c9534..58a3d221 100644
--- a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
+++ b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style shimless-rma-shared">
+<style include="cr-shared-style shimless-rma-shared shimless-fonts">
   cr-dialog::part(dialog) {
     width: 700px;
   }
@@ -21,12 +21,20 @@
   }
 
   .button-label {
-    color: black;
+    color: var(--shimless-button-label-text-color);
+    font-family: var(--shimless-button-label-font-family);
+    font-size: var(--shimless-button-label-font-size);
+    font-weight: var(--shimless-medium-font-weight);
+    line-height: var(--shimless-button-label-line-height);
     margin-bottom: 5px;
   }
 
   .button-description {
-    color: grey;
+    color: var(--shimless-instructions-text-color);
+    font-family: var(--shimless-instructions-font-family);
+    font-size: var(--shimless-instructions-font-size);
+    font-weight: var(--shimless-regular-font-weight);
+    line-height: var(--shimless-instructions-line-height);
   }
 
   .button-icon {
@@ -54,7 +62,9 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('repairCompletedTitleText')]]</h1>
-    <div>[[i18n('repairCompletedDescriptionText')]]</div>
+    <div class="instructions">
+      [[i18n('repairCompletedDescriptionText')]]
+    </div>
     <cr-button id="shutDownButton" on-click="onShutDownButtonClick_"
         disabled="[[allButtonsDisabled]]">
         [[i18n('repairCompleteShutDownButtonText')]]
@@ -134,7 +144,8 @@
     <cr-button id="closeLogDialogButton" on-click="onCancelClick_">
       [[i18n('rmaLogsCancelButtonText')]]
     </cr-button>
-    <cr-button id="rmaLogButton" on-click="onRmaLogButtonClick_">
+    <cr-button id="rmaLogButton" class="text-button pill" 
+        on-click="onRmaLogButtonClick_">
       [[i18n('rmaLogsSaveToUsbButtonText')]]
     </cr-button>
   </div>
diff --git a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
index 8e71da1..f9701f4 100644
--- a/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
+++ b/ash/webui/shimless_rma/resources/wrapup_repair_complete_page.js
@@ -6,6 +6,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
 import './base_page.js';
+import './shimless_rma_fonts_css.js';
 import './shimless_rma_shared_css.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/ash/webui/shimless_rma/resources/wrapup_restock_page.html b/ash/webui/shimless_rma/resources/wrapup_restock_page.html
index 38086de..358f37b 100644
--- a/ash/webui/shimless_rma/resources/wrapup_restock_page.html
+++ b/ash/webui/shimless_rma/resources/wrapup_restock_page.html
@@ -4,7 +4,7 @@
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('restockTitleText')]]</h1>
-    <div>[[i18n('restockInstructionsText')]]</div>
+    <div class="instructions">[[i18n('restockInstructionsText')]]</div>
     <cr-button id="continue" on-click="onRestockContinueButtonClicked_"
         disabled="[[allButtonsDisabled]]">
       [[i18n('restockContinueButtonText')]]
diff --git a/ash/webui/shimless_rma/resources/wrapup_wait_for_manual_wp_enable_page.html b/ash/webui/shimless_rma/resources/wrapup_wait_for_manual_wp_enable_page.html
index b71e268..376b2de4 100644
--- a/ash/webui/shimless_rma/resources/wrapup_wait_for_manual_wp_enable_page.html
+++ b/ash/webui/shimless_rma/resources/wrapup_wait_for_manual_wp_enable_page.html
@@ -6,7 +6,7 @@
     <h1>[[i18n('manuallyEnableWpTitleText')]]</h1>
   </div>
   <div slot="body">
-    <div id="manuallyEnableHwwpInstructions">
+    <div id="manuallyEnableHwwpInstructions" class="instructions">
       [[getBodyText_(hwwpEnabled_)]]
     </div>
   </div>
diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc
index 17d0497..86ef352 100644
--- a/ash/wm/desks/templates/desks_templates_unittest.cc
+++ b/ash/wm/desks/templates/desks_templates_unittest.cc
@@ -652,9 +652,6 @@
   // This window should be hidden whenever the desk templates grid is open.
   auto test_window = CreateAppWindow();
 
-  // This action should record deletions and grid shows in a UMA histogram.
-  base::HistogramTester histogram_tester;
-
   OpenOverviewAndShowTemplatesGrid();
 
   // The window is hidden because the desk templates grid is open.
@@ -685,13 +682,6 @@
   auto* save_template =
       GetSaveDeskAsTemplateButtonForRoot(Shell::GetPrimaryRootWindow());
   EXPECT_TRUE(save_template->IsVisible());
-
-  // Assert that histogram metrics were recorded.
-  // note: deleting a template that doesn't exist counts as a delete according
-  // to the desks model hence expected_deletes being set to two.
-  const int expected_deletes = 2;
-  histogram_tester.ExpectTotalCount(kDeleteTemplateHistogramName,
-                                    expected_deletes);
 }
 
 // Tests that the save desk as template button is aligned with the first
@@ -1728,8 +1718,248 @@
   EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText());
 }
 
+// Tests to verify that clicking the spacebar doesn't cause the name view to
+// lose focus (since it's within a button), and that whitespaces are handled
+// correctly.
+TEST_F(DesksTemplatesTest, TemplateNameTestSpaces) {
+  auto test_window = CreateAppWindow();
+
+  const std::string template_name = "desk name";
+  AddEntry(base::GUID::GenerateRandomV4(), template_name, base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+  DesksTemplatesNameView* name_view =
+      GetItemViewFromTemplatesGrid(0)->name_view();
+
+  // Pressing spacebar does not cause `name_view` to lose focus.
+  ClickOnView(name_view);
+  SendKey(ui::VKEY_RIGHT);
+  SendKey(ui::VKEY_SPACE);
+  EXPECT_TRUE(name_view->HasFocus());
+  EXPECT_EQ(base::UTF8ToUTF16(template_name) + u" ", name_view->GetText());
+
+  // Extra whitespace should be trimmed.
+  SendKey(ui::VKEY_HOME);
+  SendKey(ui::VKEY_SPACE);
+  SendKey(ui::VKEY_SPACE);
+  EXPECT_EQ(u"  " + base::UTF8ToUTF16(template_name) + u" ",
+            name_view->GetText());
+  SendKey(ui::VKEY_RETURN);
+  EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText());
+
+  // A string consisting of just spaces is considered an empty string, and the
+  // name change is reverted.
+  EXPECT_FALSE(name_view->HasFocus());
+  ClickOnView(name_view);
+  EXPECT_TRUE(name_view->HasFocus());
+  SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
+  SendKey(ui::VKEY_SPACE);
+  EXPECT_EQ(u" ", name_view->GetText());
+  SendKey(ui::VKEY_RETURN);
+  EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText());
+}
+
+// Tests that there is no crash after we use the keyboard to change the name of
+// a template. Regression test for https://crbug.com/1279649.
+TEST_F(DesksTemplatesTest, EditTemplateNameWithKeyboardNoCrash) {
+  AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+  DesksTemplatesNameView* name_view =
+      GetItemViewFromTemplatesGrid(0)->name_view();
+
+  // Tab until we focus the name view of the first template item.
+  SendKey(ui::VKEY_TAB);
+  SendKey(ui::VKEY_TAB);
+  ASSERT_EQ(name_view, GetHighlightedView());
+
+  // Rename template "a" to template "d".
+  SendKey(ui::VKEY_RETURN);
+  SendKey(ui::VKEY_D);
+  SendKey(ui::VKEY_RETURN);
+  WaitForDesksTemplatesUI();
+
+  // Verify that there is no crash after we tab again.
+  SendKey(ui::VKEY_TAB);
+}
+
+// Tests that there is no crash when leaving the template name view focused with
+// a changed name during shutdown. Regression test for
+// https://crbug.com/1281422.
+TEST_F(DesksTemplatesTest, EditTemplateNameShutdownNoCrash) {
+  // The fade out animation of the desks templates grid must be enabled for this
+  // crash to have happened.
+  animation_scale_ = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+  DesksTemplatesNameView* name_view =
+      GetItemViewFromTemplatesGrid(0)->name_view();
+
+  // Tab until we focus the name view of the first template item.
+  SendKey(ui::VKEY_TAB);
+  SendKey(ui::VKEY_TAB);
+  ASSERT_EQ(name_view, GetHighlightedView());
+
+  // Rename template "a" to template "ddd".
+  SendKey(ui::VKEY_RETURN);
+  SendKey(ui::VKEY_D);
+  SendKey(ui::VKEY_D);
+  SendKey(ui::VKEY_D);
+
+  // Verify that there is no crash while the test tears down.
+}
+
+// Tests that the hovering over the templates name shows the expected cursor.
+TEST_F(DesksTemplatesTest, TemplatesNameHitTest) {
+  auto* cursor_manager = Shell::Get()->cursor_manager();
+
+  for (bool is_rtl : {true, false}) {
+    SCOPED_TRACE(is_rtl ? "rtl" : "ltr");
+    base::i18n::SetRTLForTesting(is_rtl);
+
+    AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
+
+    OpenOverviewAndShowTemplatesGrid();
+    DesksTemplatesNameView* name_view =
+        GetItemViewFromTemplatesGrid(0)->name_view();
+    const gfx::Rect name_view_bounds = name_view->GetBoundsInScreen();
+    // Hover to a point just inside main edge. This will cover the case where
+    // the hit test logic is inverted.
+    const gfx::Point hover_point =
+        is_rtl ? name_view_bounds.right_center() + gfx::Vector2d(-2, 0)
+               : name_view_bounds.left_center() + gfx::Vector2d(2, 0);
+
+    // Tests that the hover cursor is an IBeam.
+    GetEventGenerator()->MoveMouseTo(hover_point);
+    EXPECT_EQ(ui::mojom::CursorType::kIBeam,
+              cursor_manager->GetCursor().type());
+
+    // Exit overview for the next run.
+    ToggleOverview();
+  }
+}
+
+// Tests that accessibility overrides are set as expected.
+TEST_F(DesksTemplatesTest, AccessibilityFocusAnnotatorInOverview) {
+  auto window = CreateTestWindow(gfx::Rect(100, 100));
+
+  ToggleOverview();
+  WaitForDesksTemplatesUI();
+
+  auto* focus_widget = views::Widget::GetWidgetForNativeWindow(
+      GetOverviewSession()->GetOverviewFocusWindow());
+  DCHECK(focus_widget);
+
+  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
+  auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget());
+  DCHECK(desk_widget);
+
+  auto* save_widget =
+      GetSaveDeskAsTemplateButtonForRoot(Shell::GetPrimaryRootWindow());
+
+  // Overview items are in MRU order, so the expected order in the grid list is
+  // the reverse creation order.
+  auto* item_widget = GetOverviewItemForWindow(window.get())->item_widget();
+
+  // Order should be [focus_widget, save_widget, item_widget, desk_widget].
+  CheckA11yOverrides("focus", focus_widget, desk_widget, save_widget);
+  CheckA11yOverrides("save", save_widget, focus_widget, item_widget);
+  CheckA11yOverrides("item", item_widget, save_widget, desk_widget);
+  CheckA11yOverrides("desk", desk_widget, item_widget, focus_widget);
+}
+
+TEST_F(DesksTemplatesTest, LayoutItemsInLandscape) {
+  UpdateDisplay("800x600");
+
+  // Create a window and add four test entries.
+  auto test_window = CreateAppWindow();
+  AddEntry(base::GUID::GenerateRandomV4(), "A_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "B_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "C_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "D_template", base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+
+  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
+  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
+  const auto* templates_grid_view =
+      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
+
+  // The grid has four items and one feedback button.
+  views::View::Views grid_views = templates_grid_view->children();
+  ASSERT_EQ(5ul, grid_views.size());
+
+  // We expect the first three items to be laid out in one row.
+  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[1]->bounds().y());
+  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[2]->bounds().y());
+  // The fourth item goes in the second row.
+  EXPECT_NE(grid_views[0]->bounds().y(), grid_views[3]->bounds().y());
+}
+
+TEST_F(DesksTemplatesTest, LayoutItemsInPortrait) {
+  UpdateDisplay("600x800");
+
+  // Create a window and add four test entries.
+  auto test_window = CreateAppWindow();
+  AddEntry(base::GUID::GenerateRandomV4(), "A_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "B_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "C_template", base::Time::Now());
+  AddEntry(base::GUID::GenerateRandomV4(), "D_template", base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+
+  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
+  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
+  const auto* templates_grid_view =
+      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
+
+  // The grid has four items and one feedback button.
+  views::View::Views grid_views = templates_grid_view->children();
+  ASSERT_EQ(5ul, grid_views.size());
+
+  // We expect the first two items to be laid out in one row.
+  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[1]->bounds().y());
+  // And the last two items on the next row.
+  EXPECT_NE(grid_views[0]->bounds().y(), grid_views[2]->bounds().y());
+  EXPECT_EQ(grid_views[2]->bounds().y(), grid_views[3]->bounds().y());
+}
+
+// Tests that there is no overlap with the shelf on our smallest supported
+// resolution.
+TEST_F(DesksTemplatesTest, ItemsDoNotOverlapShelf) {
+  // The smallest display resolution we support is 1087x675.
+  UpdateDisplay("1000x600");
+
+  // Create 6 entries to max out the grid.
+  for (const std::string& name : {"A", "B", "C", "D", "E", "F"})
+    AddEntry(base::GUID::GenerateRandomV4(), name, base::Time::Now());
+
+  OpenOverviewAndShowTemplatesGrid();
+
+  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
+  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
+  const auto* templates_grid_view =
+      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
+
+  // The grid has six items and one feedback button.
+  views::View::Views grid_views = templates_grid_view->children();
+  ASSERT_EQ(7ul, grid_views.size());
+
+  const gfx::Rect shelf_bounds =
+      GetPrimaryShelf()->shelf_widget()->GetWindowBoundsInScreen();
+
+  // Test that none of the grid items overlap with the shelf.
+  for (views::View* view : grid_views)
+    EXPECT_FALSE(view->GetBoundsInScreen().Intersects(shelf_bounds));
+}
+
 // Tests that showing the overview records to the TemplateGrid histogram.
-TEST_F(DesksTemplatesTest, RecordDesksTemplateGridShows) {
+TEST_F(DesksTemplatesTest, RecordDesksTemplateGridShowMetric) {
   // Make sure that LoadTemplateHistogram is recorded.
   base::HistogramTester histogram_tester;
 
@@ -1878,174 +2108,6 @@
                                     kExpectedDialogShows);
 }
 
-// Tests to verify that clicking the spacebar doesn't cause the name view to
-// lose focus (since it's within a button), and that whitespaces are handled
-// correctly.
-TEST_F(DesksTemplatesTest, TemplateNameTestSpaces) {
-  auto test_window = CreateAppWindow();
-
-  const std::string template_name = "desk name";
-  AddEntry(base::GUID::GenerateRandomV4(), template_name, base::Time::Now());
-
-  OpenOverviewAndShowTemplatesGrid();
-  DesksTemplatesNameView* name_view =
-      GetItemViewFromTemplatesGrid(0)->name_view();
-
-  // Pressing spacebar does not cause `name_view` to lose focus.
-  ClickOnView(name_view);
-  SendKey(ui::VKEY_RIGHT);
-  SendKey(ui::VKEY_SPACE);
-  EXPECT_TRUE(name_view->HasFocus());
-  EXPECT_EQ(base::UTF8ToUTF16(template_name) + u" ", name_view->GetText());
-
-  // Extra whitespace should be trimmed.
-  SendKey(ui::VKEY_HOME);
-  SendKey(ui::VKEY_SPACE);
-  SendKey(ui::VKEY_SPACE);
-  EXPECT_EQ(u"  " + base::UTF8ToUTF16(template_name) + u" ",
-            name_view->GetText());
-  SendKey(ui::VKEY_RETURN);
-  EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText());
-
-  // A string consisting of just spaces is considered an empty string, and the
-  // name change is reverted.
-  EXPECT_FALSE(name_view->HasFocus());
-  ClickOnView(name_view);
-  EXPECT_TRUE(name_view->HasFocus());
-  SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
-  SendKey(ui::VKEY_SPACE);
-  EXPECT_EQ(u" ", name_view->GetText());
-  SendKey(ui::VKEY_RETURN);
-  EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText());
-}
-
-// Tests that there is no crash after we use the keyboard to change the name of
-// a template. Regression test for https://crbug.com/1279649.
-TEST_F(DesksTemplatesTest, EditTemplateNameWithKeyboardNoCrash) {
-  AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now());
-
-  OpenOverviewAndShowTemplatesGrid();
-  DesksTemplatesNameView* name_view =
-      GetItemViewFromTemplatesGrid(0)->name_view();
-
-  // Tab until we focus the name view of the first template item.
-  SendKey(ui::VKEY_TAB);
-  SendKey(ui::VKEY_TAB);
-  ASSERT_EQ(name_view, GetHighlightedView());
-
-  // Rename template "a" to template "d".
-  SendKey(ui::VKEY_RETURN);
-  SendKey(ui::VKEY_D);
-  SendKey(ui::VKEY_RETURN);
-  WaitForDesksTemplatesUI();
-
-  // Verify that there is no crash after we tab again.
-  SendKey(ui::VKEY_TAB);
-}
-
-// Tests that there is no crash when leaving the template name view focused with
-// a changed name during shutdown. Regression test for
-// https://crbug.com/1281422.
-TEST_F(DesksTemplatesTest, EditTemplateNameShutdownNoCrash) {
-  // The fade out animation of the desks templates grid must be enabled for this
-  // crash to have happened.
-  animation_scale_ = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
-      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-
-  AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now());
-
-  OpenOverviewAndShowTemplatesGrid();
-  DesksTemplatesNameView* name_view =
-      GetItemViewFromTemplatesGrid(0)->name_view();
-
-  // Tab until we focus the name view of the first template item.
-  SendKey(ui::VKEY_TAB);
-  SendKey(ui::VKEY_TAB);
-  ASSERT_EQ(name_view, GetHighlightedView());
-
-  // Rename template "a" to template "ddd".
-  SendKey(ui::VKEY_RETURN);
-  SendKey(ui::VKEY_D);
-  SendKey(ui::VKEY_D);
-  SendKey(ui::VKEY_D);
-
-  // Verify that there is no crash while the test tears down.
-}
-
-// Tests that the hovering over the templates name shows the expected cursor.
-TEST_F(DesksTemplatesTest, TemplatesNameHitTest) {
-  auto* cursor_manager = Shell::Get()->cursor_manager();
-
-  for (bool is_rtl : {true, false}) {
-    SCOPED_TRACE(is_rtl ? "rtl" : "ltr");
-    base::i18n::SetRTLForTesting(is_rtl);
-
-    AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now());
-
-    OpenOverviewAndShowTemplatesGrid();
-    DesksTemplatesNameView* name_view =
-        GetItemViewFromTemplatesGrid(0)->name_view();
-    const gfx::Rect name_view_bounds = name_view->GetBoundsInScreen();
-    // Hover to a point just inside main edge. This will cover the case where
-    // the hit test logic is inverted.
-    const gfx::Point hover_point =
-        is_rtl ? name_view_bounds.right_center() + gfx::Vector2d(-2, 0)
-               : name_view_bounds.left_center() + gfx::Vector2d(2, 0);
-
-    // Tests that the hover cursor is an IBeam.
-    GetEventGenerator()->MoveMouseTo(hover_point);
-    EXPECT_EQ(ui::mojom::CursorType::kIBeam,
-              cursor_manager->GetCursor().type());
-
-    // Exit overview for the next run.
-    ToggleOverview();
-  }
-}
-
-TEST_F(DesksTemplatesTest, UserTemplateCountRecordsCorrectly) {
-  // Record histogram.
-  base::HistogramTester histogram_tester;
-
-  // Create three new templates through the UI.
-  for (unsigned long num_templates = 0; num_templates < 3; ++num_templates) {
-    // There are no saved template entries and one test window initially.
-    auto test_window = CreateAppWindow();
-
-    // Toggle overview if there isn't currently an overview. This is needed
-    // to save a template via the UI.
-    if (!GetOverviewSession()) {
-      ToggleOverview();
-      WaitForDesksTemplatesUI();
-    }
-
-    // The `save_desk_as_template_widget` is visible when at least one window is
-    // open.
-    views::Widget* save_desk_as_template_widget =
-        GetSaveDeskAsTemplateButtonForRoot(Shell::GetPrimaryRootWindow());
-    ASSERT_TRUE(save_desk_as_template_widget);
-    EXPECT_TRUE(save_desk_as_template_widget->GetContentsView()->GetVisible());
-
-    // Click on `save_desk_as_template_widget` button.
-    ClickOnView(save_desk_as_template_widget->GetContentsView());
-    ASSERT_EQ(num_templates + 1, GetAllEntries().size());
-
-    // Expect that the Desk Templates grid is visible.
-    EXPECT_TRUE(GetOverviewGridList()[0]->IsShowingDesksTemplatesGrid());
-  }
-
-  OpenOverviewAndShowTemplatesGrid();
-
-  // Delete one of the templates which will iterate the histogram's second
-  // bucket.
-  DeleteTemplate(GetAllEntries()[0]->uuid(), /*expected_item_count=*/3);
-
-  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 1, 1);
-  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 2, 2);
-  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 3, 1);
-}
-
 // Tests that the window and tab counts are properly recorded in their
 // resepctive metrics.
 TEST_F(DesksTemplatesTest, SaveDeskRecordsWindowAndTabCountMetrics) {
@@ -2104,122 +2166,51 @@
   histogram_tester.ExpectBucketCount(kWindowAndTabCountHistogramName, 6, 1);
 }
 
-// Tests that accessibility overrides are set as expected.
-TEST_F(DesksTemplatesTest, AccessibilityFocusAnnotatorInOverview) {
-  auto window = CreateTestWindow(gfx::Rect(100, 100));
+// Tests that the user template count metric is recorded correctly.
+TEST_F(DesksTemplatesTest, UserTemplateCountRecordsMetricCorrectly) {
+  // Record histogram.
+  base::HistogramTester histogram_tester;
 
-  ToggleOverview();
-  WaitForDesksTemplatesUI();
+  // Create three new templates through the UI.
+  for (unsigned long num_templates = 0; num_templates < 3; ++num_templates) {
+    // There are no saved template entries and one test window initially.
+    auto test_window = CreateAppWindow();
 
-  auto* focus_widget = views::Widget::GetWidgetForNativeWindow(
-      GetOverviewSession()->GetOverviewFocusWindow());
-  DCHECK(focus_widget);
+    // Toggle overview if there isn't currently an overview. This is needed
+    // to save a template via the UI.
+    if (!GetOverviewSession()) {
+      ToggleOverview();
+      WaitForDesksTemplatesUI();
+    }
 
-  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
-  auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget());
-  DCHECK(desk_widget);
+    // The `save_desk_as_template_widget` is visible when at least one window is
+    // open.
+    views::Widget* save_desk_as_template_widget =
+        GetSaveDeskAsTemplateButtonForRoot(Shell::GetPrimaryRootWindow());
+    ASSERT_TRUE(save_desk_as_template_widget);
+    EXPECT_TRUE(save_desk_as_template_widget->GetContentsView()->GetVisible());
 
-  auto* save_widget =
-      GetSaveDeskAsTemplateButtonForRoot(Shell::GetPrimaryRootWindow());
+    // Click on `save_desk_as_template_widget` button.
+    ClickOnView(save_desk_as_template_widget->GetContentsView());
+    ASSERT_EQ(num_templates + 1, GetAllEntries().size());
 
-  // Overview items are in MRU order, so the expected order in the grid list is
-  // the reverse creation order.
-  auto* item_widget = GetOverviewItemForWindow(window.get())->item_widget();
-
-  // Order should be [focus_widget, save_widget, item_widget, desk_widget].
-  CheckA11yOverrides("focus", focus_widget, desk_widget, save_widget);
-  CheckA11yOverrides("save", save_widget, focus_widget, item_widget);
-  CheckA11yOverrides("item", item_widget, save_widget, desk_widget);
-  CheckA11yOverrides("desk", desk_widget, item_widget, focus_widget);
-}
-
-TEST_F(DesksTemplatesTest, LayoutItemsInLandscape) {
-  UpdateDisplay("800x600");
-
-  // Create a window and add four test entries.
-  auto test_window = CreateAppWindow();
-  AddEntry(base::GUID::GenerateRandomV4(), "A_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "B_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "C_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "D_template", base::Time::Now());
+    // Expect that the Desk Templates grid is visible.
+    EXPECT_TRUE(GetOverviewGridList()[0]->IsShowingDesksTemplatesGrid());
+  }
 
   OpenOverviewAndShowTemplatesGrid();
 
-  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
-  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
-  const auto* templates_grid_view =
-      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
+  // Delete one of the templates which will iterate the histogram's second
+  // bucket.
+  DeleteTemplate(GetAllEntries()[0]->uuid(), /*expected_item_count=*/3);
 
-  // The grid has four items and one feedback button.
-  views::View::Views grid_views = templates_grid_view->children();
-  ASSERT_EQ(5ul, grid_views.size());
-
-  // We expect the first three items to be laid out in one row.
-  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[1]->bounds().y());
-  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[2]->bounds().y());
-  // The fourth item goes in the second row.
-  EXPECT_NE(grid_views[0]->bounds().y(), grid_views[3]->bounds().y());
-}
-
-TEST_F(DesksTemplatesTest, LayoutItemsInPortrait) {
-  UpdateDisplay("600x800");
-
-  // Create a window and add four test entries.
-  auto test_window = CreateAppWindow();
-  AddEntry(base::GUID::GenerateRandomV4(), "A_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "B_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "C_template", base::Time::Now());
-  AddEntry(base::GUID::GenerateRandomV4(), "D_template", base::Time::Now());
-
-  OpenOverviewAndShowTemplatesGrid();
-
-  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
-  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
-  const auto* templates_grid_view =
-      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
-
-  // The grid has four items and one feedback button.
-  views::View::Views grid_views = templates_grid_view->children();
-  ASSERT_EQ(5ul, grid_views.size());
-
-  // We expect the first two items to be laid out in one row.
-  EXPECT_EQ(grid_views[0]->bounds().y(), grid_views[1]->bounds().y());
-  // And the last two items on the next row.
-  EXPECT_NE(grid_views[0]->bounds().y(), grid_views[2]->bounds().y());
-  EXPECT_EQ(grid_views[2]->bounds().y(), grid_views[3]->bounds().y());
-}
-
-// Tests that there is no overlap with the shelf on our smallest supported
-// resolution.
-TEST_F(DesksTemplatesTest, ItemsDoNotOverlapShelf) {
-  // The smallest display resolution we support is 1087x675.
-  UpdateDisplay("1000x600");
-
-  // Create 6 entries to max out the grid.
-  for (const std::string& name : {"A", "B", "C", "D", "E", "F"})
-    AddEntry(base::GUID::GenerateRandomV4(), name, base::Time::Now());
-
-  OpenOverviewAndShowTemplatesGrid();
-
-  OverviewGrid* overview_grid = GetOverviewGridList()[0].get();
-  views::Widget* grid_widget = overview_grid->desks_templates_grid_widget();
-  const auto* templates_grid_view =
-      static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView());
-
-  // The grid has six items and one feedback button.
-  views::View::Views grid_views = templates_grid_view->children();
-  ASSERT_EQ(7ul, grid_views.size());
-
-  const gfx::Rect shelf_bounds =
-      GetPrimaryShelf()->shelf_widget()->GetWindowBoundsInScreen();
-
-  // Test that none of the grid items overlap with the shelf.
-  for (views::View* view : grid_views)
-    EXPECT_FALSE(view->GetBoundsInScreen().Intersects(shelf_bounds));
+  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 1, 1);
+  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 2, 2);
+  histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 3, 1);
 }
 
 // Tests record metrics when current template being replaced.
-TEST_F(DesksTemplatesTest, ReplaceTemplate) {
+TEST_F(DesksTemplatesTest, ReplaceTemplateMetric) {
   base::HistogramTester histogram_tester;
 
   UpdateDisplay("800x600,800x600");
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 8a3a284..fcf03a8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3949,6 +3949,7 @@
       "android/java/src/org/chromium/base/FeatureList.java",
       "android/java/src/org/chromium/base/Features.java",
       "android/java/src/org/chromium/base/FieldTrialList.java",
+      "android/java/src/org/chromium/base/FileUtils.java",
       "android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
       "android/java/src/org/chromium/base/IntStringCallback.java",
       "android/java/src/org/chromium/base/JNIUtils.java",
diff --git a/base/android/java/src/org/chromium/base/FileUtils.java b/base/android/java/src/org/chromium/base/FileUtils.java
index db295e8..1c38298 100644
--- a/base/android/java/src/org/chromium/base/FileUtils.java
+++ b/base/android/java/src/org/chromium/base/FileUtils.java
@@ -13,6 +13,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -26,6 +29,7 @@
 /**
  * Helper methods for dealing with Files.
  */
+@JNINamespace("base::android")
 public class FileUtils {
     private static final String TAG = "FileUtils";
 
@@ -208,4 +212,22 @@
         }
         return null;
     }
+
+    /**
+     * Gets the canonicalised absolute pathname for |filePath|. Returns empty string if the path is
+     * invalid. This function can result in I/O so it can be slow.
+     * @param filePath Path of the file, has to be a file path instead of a content URI.
+     * @return canonicalised absolute pathname for |filePath|.
+     */
+    public static String getAbsoluteFilePath(String filePath) {
+        return FileUtilsJni.get().getAbsoluteFilePath(filePath);
+    }
+
+    @NativeMethods
+    public interface Natives {
+        /**
+         * Returns the canonicalised absolute pathname for |filePath|.
+         */
+        String getAbsoluteFilePath(String filePath);
+    }
 }
diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java
index 95ddded..201cb315 100644
--- a/base/android/java/src/org/chromium/base/library_loader/Linker.java
+++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -420,7 +420,14 @@
             // nullified yet. With an invalid load address it is impossible to locate the RELRO
             // region in the current process. This could happen when the library loaded successfully
             // only after the fallback to no sharing.
-            return mRemoteLibInfo.mLoadAddress != 0;
+            //
+            // TODO(pasko): There is no need to check for |mLoadAddress| here because in the worst
+            // case the zero address will be ignored on the native side of the
+            // atomicReplaceRelroLocked(). The takeSharedRelrosFromBundle() relies on zero addresses
+            // being ignored in native anyway. It seems the only effect of removing this check here
+            // will be extra added samples to the RelroSharingStatus2 histogram. This will be a tiny
+            // bit smoother to do after M99.
+            return mLocalLibInfo.mLoadAddress != 0;
         }
         return false;
     }
diff --git a/base/android/linker/modern_linker_jni.cc b/base/android/linker/modern_linker_jni.cc
index 4917fd22..9c4a19a 100644
--- a/base/android/linker/modern_linker_jni.cc
+++ b/base/android/linker/modern_linker_jni.cc
@@ -524,7 +524,7 @@
     return false;
   }
 
-  if (other_lib_info.load_address_ == 0) {
+  if (load_address_ == 0) {
     LOG_ERROR("Load address reset. Second attempt to load the library?");
     s_relro_sharing_status = RelroSharingStatus::EXTERNAL_LOAD_ADDRESS_RESET;
     return false;
diff --git a/base/android/linker/modern_linker_jni.h b/base/android/linker/modern_linker_jni.h
index e8a2d08..6b3eafb 100644
--- a/base/android/linker/modern_linker_jni.h
+++ b/base/android/linker/modern_linker_jni.h
@@ -118,6 +118,7 @@
   // unittest LoadLibrary() directly.
   bool CreateSharedRelroFdForTesting();
 
+  void set_relro_fd_for_testing(int fd) { relro_fd_ = fd; }
   int get_relro_fd_for_testing() const { return relro_fd_; }
   size_t get_relro_start_for_testing() const { return relro_start_; }
   size_t get_load_size_for_testing() const { return load_size_; }
diff --git a/base/android/linker/modern_linker_unittest.cc b/base/android/linker/modern_linker_unittest.cc
index c9a921a2..a932123 100644
--- a/base/android/linker/modern_linker_unittest.cc
+++ b/base/android/linker/modern_linker_unittest.cc
@@ -239,6 +239,15 @@
             lib_info.load_address() + lib_info.get_load_size_for_testing());
 }
 
+TEST_F(ModernLinkerTest, FindLibraryRangesWhenLoadAddressWasReset) {
+  NativeLibInfo other_lib_info = {0, 0};
+  uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
+  other_lib_info.set_load_address(executable_start);
+  other_lib_info.set_relro_fd_for_testing(123);
+  NativeLibInfo lib_info = {0, 0};
+  EXPECT_FALSE(lib_info.CompareRelroAndReplaceItBy(other_lib_info));
+}
+
 // Check that discovering RELRO segment address ranges and the DSO ranges agrees
 // with the method based on dl_iterate_phdr(3). The check is performed on the
 // test library, not on libmonochrome.
diff --git a/base/containers/vector_buffer.h b/base/containers/vector_buffer.h
index b48365d9f..79aac63 100644
--- a/base/containers/vector_buffer.h
+++ b/base/containers/vector_buffer.h
@@ -8,15 +8,12 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <ios>
-#include <ostream>
 #include <type_traits>
 #include <utility>
 
 #include "base/check_op.h"
 #include "base/containers/util.h"
 #include "base/numerics/checked_math.h"
-#include "build/build_config.h"
 
 namespace base {
 namespace internal {
@@ -130,11 +127,7 @@
             typename std::enable_if<base::is_trivially_copyable<T2>::value,
                                     int>::type = 0>
   static void MoveRange(T* from_begin, T* from_end, T* to) {
-    CHECK(!RangesOverlap(from_begin, from_end, to))
-        // TODO(crbug.com/1172816): Remove logging once root cause is found.
-        << std::hex << "from_begin: 0x" << get_uintptr(from_begin)
-        << ", from_end: 0x" << get_uintptr(from_end) << ", to: 0x"
-        << get_uintptr(to);
+    CHECK(!RangesOverlap(from_begin, from_end, to));
 
     memcpy(
         to, from_begin,
diff --git a/base/files/file_util_android.cc b/base/files/file_util_android.cc
index b8b3b37..ca068ca 100644
--- a/base/files/file_util_android.cc
+++ b/base/files/file_util_android.cc
@@ -4,10 +4,30 @@
 
 #include "base/files/file_util.h"
 
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/base_jni_headers/FileUtils_jni.h"
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaLocalRef;
+
 namespace base {
+namespace android {
+
+static ScopedJavaLocalRef<jstring> JNI_FileUtils_GetAbsoluteFilePath(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& j_file_path) {
+  base::FilePath file_path(
+      base::android::ConvertJavaStringToUTF8(env, j_file_path));
+  base::FilePath absolute_file_path = MakeAbsoluteFilePath(file_path);
+  return base::android::ConvertUTF8ToJavaString(env,
+                                                absolute_file_path.value());
+}
+
+}  // namespace android
 
 bool GetShmemTempDir(bool executable, base::FilePath* path) {
   return PathService::Get(base::DIR_CACHE, path);
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 2d1db51..8fa3de0c 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -2045,7 +2045,7 @@
     } else {
       cflags = [ "-Os" ] + common_optimize_on_cflags
     }
-  } else if (is_chromeos_ash) {
+  } else if (is_chromeos) {
     # TODO(gbiv): This is partially favoring size over speed. CrOS exclusively
     # uses clang, and -Os in clang is more of a size-conscious -O2 than "size at
     # any cost" (AKA -Oz). It'd be nice to:
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index eeeb4e0e..9266f95 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -645,6 +645,7 @@
   "java/res/layout/managed_by_menu_item.xml",
   "java/res/layout/material_tooltip.xml",
   "java/res/layout/multiline_spinner_item.xml",
+  "java/res/layout/mv_tiles_layout.xml",
   "java/res/layout/navigation_bubble.xml",
   "java/res/layout/navigation_sheet.xml",
   "java/res/layout/navigation_sheet_toolbar.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 02248db..5c7ab01 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1057,6 +1057,10 @@
   "java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSites.java",
   "java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java",
   "java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesMetadataUtils.java",
+  "java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListCoordinator.java",
+  "java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListProperties.java",
+  "java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinder.java",
+  "java/src/org/chromium/chrome/browser/suggestions/tile/MvTilesLayout.java",
   "java/src/org/chromium/chrome/browser/suggestions/tile/SiteSectionViewHolder.java",
   "java/src/org/chromium/chrome/browser/suggestions/tile/SuggestionsTileView.java",
   "java/src/org/chromium/chrome/browser/suggestions/tile/Tile.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 5a3768a..baf0130d 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -193,6 +193,7 @@
   "junit/src/org/chromium/chrome/browser/signin/SyncConsentActivityLauncherImplTest.java",
   "junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java",
   "junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java",
+  "junit/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinderUnitTest.java",
   "junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java",
   "junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerTest.java",
   "junit/src/org/chromium/chrome/browser/survey/SurveyHttpClientBridgeUnitTest.java",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingWithPopupCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingWithPopupCoordinator.java
index b8ecde3..8235e40 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingWithPopupCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/onboarding/BottomSheetOnboardingWithPopupCoordinator.java
@@ -36,6 +36,8 @@
     private static final String SPLIT_ONBOARDING_SHOW_DIALOG_KEY = "split_onboarding_show_dialog";
     private static final String SPLIT_ONBOARDING_ACCEPT_DIALOG_KEY = "split_onboarding_accept";
     private static final String SPLIT_ONBOARDING_CLOSE_DIALOG_KEY = "split_onboarding_decline";
+    private static final String SPLIT_ONBOARDING_TITLE_KEY = "split_onboarding_title";
+    private static final String SPLIT_ONBOARDING_SUBTITLE_KEY = "split_onboarding_text";
     // We have a bit more space in the dialog, so we add line spacing to make it easier to read the
     // terms.
     private static final float TERMS_LINE_SPACING_MULTIPLIER = 1.25f;
@@ -96,9 +98,18 @@
             ButtonCompat bottomSheetNoButton = mView.findViewById(R.id.button_init_not_ok);
             bottomSheetNoButton.setText(mStringMap.get(SPLIT_ONBOARDING_CLOSE_BOTTOMSHEET_KEY));
         }
-
-        updateTitleView(mView.findViewById(R.id.onboarding_try_assistant));
-        updateSubtitleView(mView.findViewById(R.id.onboarding_subtitle));
+        TextView titleView = mView.findViewById(R.id.onboarding_try_assistant);
+        if (mStringMap.containsKey(SPLIT_ONBOARDING_TITLE_KEY)) {
+            titleView.setText(mStringMap.get(SPLIT_ONBOARDING_TITLE_KEY));
+        } else {
+            updateTitleView(titleView);
+        }
+        TextView subtitleView = mView.findViewById(R.id.onboarding_subtitle);
+        if (mStringMap.containsKey(SPLIT_ONBOARDING_SUBTITLE_KEY)) {
+            subtitleView.setText(mStringMap.get(SPLIT_ONBOARDING_SUBTITLE_KEY));
+        } else {
+            updateSubtitleView(subtitleView);
+        }
     }
 
     private void showDialog(Callback<Integer> callback) {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
index f9002c7..101109e 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetOnboardingCoordinatorTest.java
@@ -421,8 +421,12 @@
         String expectedConfirmDialogText = "Accept";
         String expectedCloseDialogText = "Reject";
 
-        coordinator.addEntryToStringMap("onboarding_title", expectedTitle);
-        coordinator.addEntryToStringMap("onboarding_text", expectedMessage);
+        // If both are provided, split_onboarding_* should take precedence
+        coordinator.addEntryToStringMap("onboarding_title", "Should be ignored");
+        coordinator.addEntryToStringMap("onboarding_text", "Should be ignored");
+        coordinator.addEntryToStringMap("split_onboarding_title", expectedTitle);
+        coordinator.addEntryToStringMap("split_onboarding_text", expectedMessage);
+
         coordinator.addEntryToStringMap("terms_and_conditions", expectedTerms);
         coordinator.addEntryToStringMap("terms_and_conditions_url", expectedTermsUrl);
         coordinator.addEntryToStringMap(
@@ -497,8 +501,12 @@
         String expectedConfirmDialogText = "Accept";
         String expectedCloseDialogText = "Reject";
 
-        coordinator.addEntryToStringMap("onboarding_title", expectedTitle);
-        coordinator.addEntryToStringMap("onboarding_text", expectedMessage);
+        // If both are provided, split_onboarding_* should take precedence
+        coordinator.addEntryToStringMap("onboarding_title", "Should be ignored");
+        coordinator.addEntryToStringMap("onboarding_text", "Should be ignored");
+        coordinator.addEntryToStringMap("split_onboarding_title", expectedTitle);
+        coordinator.addEntryToStringMap("split_onboarding_text", expectedMessage);
+
         coordinator.addEntryToStringMap("terms_and_conditions", expectedTerms);
         coordinator.addEntryToStringMap("terms_and_conditions_url", expectedTermsUrl);
         coordinator.addEntryToStringMap(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
index 9ba6b9fe..01b680ce 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
@@ -181,6 +181,22 @@
         }
 
         @Override
+        public void setPriorityNotification(String feature) {}
+
+        @Override
+        @Nullable
+        public String getPendingPriorityNotification() {
+            return null;
+        }
+
+        @Override
+        public void registerPriorityNotificationHandler(
+                String feature, Runnable priorityNotificationHandler) {}
+
+        @Override
+        public void unregisterPriorityNotificationHandler(String feature) {}
+
+        @Override
         public boolean isInitialized() {
             return true;
         }
diff --git a/chrome/android/features/start_surface/internal/java/res/values/dimens.xml b/chrome/android/features/start_surface/internal/java/res/values/dimens.xml
index 39c0b0e9..f6230564 100644
--- a/chrome/android/features/start_surface/internal/java/res/values/dimens.xml
+++ b/chrome/android/features/start_surface/internal/java/res/values/dimens.xml
@@ -5,7 +5,7 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <dimen name="ss_bottom_bar_height">64dp</dimen>
     <dimen name="tasks_surface_body_top_margin">16dp</dimen>
-    <dimen name="mv_tiles_container_top_margin">14dp</dimen>
+    <dimen name="mv_tiles_container_top_margin">17dp</dimen>
     <dimen name="tab_switcher_title_top_margin">16dp</dimen>
     <dimen name="fake_search_box_top_margin">24dp</dimen>
 </resources>
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
index bca4a63..7fe4494 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
@@ -49,9 +49,9 @@
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
+import org.chromium.chrome.browser.suggestions.tile.MvTilesLayout;
 import org.chromium.chrome.browser.suggestions.tile.SuggestionsTileView;
 import org.chromium.chrome.browser.tab.TabLaunchType;
-import org.chromium.chrome.browser.tasks.MvTilesLayout;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index 59babfb..5b0e769 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -63,7 +63,6 @@
     "java/res/layout/incognito_description_container_layout.xml",
     "java/res/layout/iph_drag_and_drop_dialog_layout.xml",
     "java/res/layout/large_message_card_item.xml",
-    "java/res/layout/mv_tiles_layout.xml",
     "java/res/layout/new_tab_tile_card_item.xml",
     "java/res/layout/price_card.xml",
     "java/res/layout/price_tracking_dialog_layout.xml",
@@ -80,6 +79,7 @@
     "java/res/layout/tab_selection_editor_layout.xml",
     "java/res/layout/tab_selection_editor_toolbar.xml",
     "java/res/layout/tab_strip_item.xml",
+    "java/res/layout/tasks_surface_search_box_layout.xml",
     "java/res/layout/tasks_view_layout.xml",
     "java/res/values/attrs.xml",
     "java/res/values/colors.xml",
@@ -96,9 +96,6 @@
 
 android_library("java") {
   sources = [
-    "java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java",
-    "java/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinder.java",
-    "java/src/org/chromium/chrome/browser/tasks/MvTilesLayout.java",
     "java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java",
     "java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherMediator.java",
     "java/src/org/chromium/chrome/browser/tasks/SingleTabView.java",
diff --git a/chrome/android/features/tab_ui/java/res/layout/mv_tiles_layout.xml b/chrome/android/features/tab_ui/java/res/layout/mv_tiles_layout.xml
deleted file mode 100644
index 83707ef..0000000
--- a/chrome/android/features/tab_ui/java/res/layout/mv_tiles_layout.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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. -->
-
-<!-- A site suggestion tile. -->
-<org.chromium.chrome.browser.tasks.MvTilesLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/mv_tiles_layout"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:orientation="horizontal">
-</org.chromium.chrome.browser.tasks.MvTilesLayout>
diff --git a/chrome/android/features/tab_ui/java/res/layout/tasks_surface_search_box_layout.xml b/chrome/android/features/tab_ui/java/res/layout/tasks_surface_search_box_layout.xml
new file mode 100644
index 0000000..9cd0aca
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/layout/tasks_surface_search_box_layout.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/search_box"
+    class="org.chromium.chrome.browser.ntp.search.SearchBoxContainerView"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/ntp_search_box_height"
+    android:layout_marginTop="0dp"
+    android:layout_marginStart="12dp"
+    android:layout_marginEnd="12dp"
+    android:paddingStart="@dimen/search_box_start_padding"
+    android:paddingEnd="10dp"
+    android:background="@drawable/ntp_search_box"
+    android:gravity="center_vertical"
+    android:orientation="horizontal">
+
+  <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
+  <RelativeLayout
+      android:layout_width="0dp"
+      android:layout_height="match_parent"
+      android:layout_weight="1"
+      android:layout_marginEnd="12dp"
+      android:gravity="center_vertical">
+    <EditText
+        android:id="@+id/search_box_text"
+        style="@style/TextAppearance.TasksSurfaceSearchBoxText"
+        android:layout_width="match_parent"
+        android:layout_height="24dp"
+        android:background="@null"
+        android:ellipsize="end"
+        android:focusable="false"
+        android:focusableInTouchMode="false"
+        android:hint="@string/search_or_type_web_address"
+        android:inputType="text"
+        android:singleLine="true"
+        tools:ignore="Autofill,LabelFor" />
+  </RelativeLayout>
+  <org.chromium.ui.widget.ChromeImageView
+      android:id="@+id/voice_search_button"
+      style="@style/LocationBarActionButton"
+      android:layout_width="20dp"
+      android:layout_height="20dp"
+      android:contentDescription="@string/accessibility_toolbar_btn_mic"
+      android:src="@drawable/btn_mic" />
+  <org.chromium.ui.widget.ChromeImageView
+      android:id="@+id/lens_camera_button"
+      style="@style/LocationBarActionButton"
+      android:layout_width="20dp"
+      android:layout_height="20dp"
+      android:layout_marginStart="16dp"
+      android:contentDescription="@string/accessibility_btn_lens_camera"
+      android:src="@drawable/lens_camera_icon"
+      android:visibility="gone" />
+</view>
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml b/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml
index f982ebb3..7ab8c88 100644
--- a/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml
@@ -27,11 +27,9 @@
             android:id="@+id/fake_search_box"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginStart="2dp"
-            android:layout_marginEnd="2dp"
             android:orientation="vertical"
             app:layout_scrollFlags="scroll">
-            <include layout="@layout/fake_search_box_layout"/>
+            <include layout="@layout/tasks_surface_search_box_layout"/>
         </LinearLayout>
         <FrameLayout android:id="@+id/query_tiles_container"
             android:layout_width="match_parent"
@@ -43,14 +41,7 @@
             app:layout_scrollFlags="scroll">
             <include layout="@layout/query_tiles_layout" />
         </FrameLayout>
-        <HorizontalScrollView android:id="@+id/mv_tiles_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            android:scrollbars="none"
-            app:layout_scrollFlags="scroll">
-            <include layout="@layout/mv_tiles_layout" />
-        </HorizontalScrollView>
+        <include layout="@layout/mv_tiles_layout" />
         <LinearLayout
             android:id="@+id/tab_switcher_title"
             android:layout_width="match_parent"
diff --git a/chrome/android/features/tab_ui/java/res/values/dimens.xml b/chrome/android/features/tab_ui/java/res/values/dimens.xml
index 1fd75ca..d6c2396 100644
--- a/chrome/android/features/tab_ui/java/res/values/dimens.xml
+++ b/chrome/android/features/tab_ui/java/res/values/dimens.xml
@@ -75,4 +75,7 @@
     <integer name="tab_thumbnail_placeholder_selected_color_alpha">38</integer>
     <integer name="tab_grid_hovered_card_background_color_alpha">128</integer>
     <integer name="tab_grid_hovered_card_background_selected_color_alpha">26</integer>
+
+    <dimen name="tasks_surface_location_bar_url_text_size">18sp</dimen>
+    <dimen name="search_box_start_padding">16dp</dimen>
 </resources>
diff --git a/chrome/android/features/tab_ui/java/res/values/styles.xml b/chrome/android/features/tab_ui/java/res/values/styles.xml
index 42f4227..ba6e661 100644
--- a/chrome/android/features/tab_ui/java/res/values/styles.xml
+++ b/chrome/android/features/tab_ui/java/res/values/styles.xml
@@ -126,4 +126,9 @@
         <item name="chipColor">@color/default_text_color_inverse</item>
     </style>
 
+    <style name="TextAppearance.TasksSurfaceSearchBoxText">
+        <item name="android:textSize">@dimen/tasks_surface_location_bar_url_text_size</item>
+        <item name="android:textColorHint">@color/search_box_hint</item>
+    </style>
+
 </resources>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
index 158393f..c921655 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
@@ -29,9 +29,12 @@
 import org.chromium.chrome.browser.query_tiles.QueryTileSection;
 import org.chromium.chrome.browser.query_tiles.QueryTileSection.QueryInfo;
 import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.suggestions.tile.MostVisitedListCoordinator;
+import org.chromium.chrome.browser.suggestions.tile.MvTilesLayout;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.mv_tiles.MostVisitedTileNavigationDelegate;
 import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegate.TabSwitcherType;
 import org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
@@ -62,8 +65,12 @@
     private final Supplier<DynamicResourceLoader> mDynamicResourceLoaderSupplier;
     private final TabContentManager mTabContentManager;
     private final ModalDialogManager mModalDialogManager;
+    private final Activity mActivity;
+    private final Supplier<Tab> mParentTabSupplier;
 
-    /** This flag should be reset once {@link mMostVisitedList#destroyMVTiles()} is called. */
+    /**
+     * This flag should be reset once {@link MostVisitedListCoordinator#destroyMVTiles} is called.
+     */
     private boolean mIsMVTilesInitialized;
 
     /** {@see TabManagementDelegate#createTasksSurface} */
@@ -82,6 +89,7 @@
             @NonNull Supplier<ShareDelegate> shareDelegateSupplier,
             @NonNull MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
             @NonNull ViewGroup rootView) {
+        mActivity = activity;
         mView = (TasksView) LayoutInflater.from(activity).inflate(R.layout.tasks_view_layout, null);
         mView.initialize(activityLifecycleDispatcher,
                 parentTabSupplier.hasValue() && parentTabSupplier.get().isIncognito(),
@@ -94,6 +102,7 @@
         mDynamicResourceLoaderSupplier = dynamicResourceLoaderSupplier;
         mTabContentManager = tabContentManager;
         mModalDialogManager = modalDialogManager;
+        mParentTabSupplier = parentTabSupplier;
         if (tabSwitcherType == TabSwitcherType.CAROUSEL) {
             mTabSwitcher = TabManagementModuleProvider.getDelegate().createCarouselTabSwitcher(
                     activity, activityLifecycleDispatcher, tabModelSelector, tabContentManager,
@@ -130,8 +139,8 @@
 
         if (hasMVTiles) {
             MvTilesLayout mvTilesLayout = mView.findViewById(R.id.mv_tiles_layout);
-            mMostVisitedList = new MostVisitedListCoordinator(activity, mvTilesLayout,
-                    mPropertyModel, parentTabSupplier, snackbarManager, windowAndroid);
+            mMostVisitedList = new MostVisitedListCoordinator(
+                    activity, mvTilesLayout, mPropertyModel, snackbarManager, windowAndroid);
             mMostVisitedList.initialize();
         }
         if (hasQueryTiles) {
@@ -152,7 +161,8 @@
     public void initialize() {
         assert LibraryLoader.getInstance().isInitialized();
         if (!mIsMVTilesInitialized && mMostVisitedList != null) {
-            mMostVisitedList.initWithNative();
+            mMostVisitedList.initWithNative(new MostVisitedTileNavigationDelegate(
+                    mActivity, Profile.getLastUsedRegularProfile(), mParentTabSupplier));
             mIsMVTilesInitialized = true;
         }
         mMediator.initialize();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
index f46b181..0548e2f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.tasks;
 
-import static org.chromium.chrome.browser.tasks.MostVisitedListProperties.IS_VISIBLE;
+import static org.chromium.chrome.browser.suggestions.tile.MostVisitedListProperties.IS_VISIBLE;
 
 import android.text.TextWatcher;
 import android.view.View;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java
new file mode 100644
index 0000000..ca661aa
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java
@@ -0,0 +1,97 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tasks.mv_tiles;
+
+import android.app.Activity;
+
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
+import org.chromium.chrome.browser.offlinepages.RequestCoordinatorBridge;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
+import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.mojom.WindowOpenDisposition;
+
+/**
+ * Extension of {@link SuggestionsNavigationDelegate} with specific methods of MV tiles on Start
+ * Surface.
+ */
+public class MostVisitedTileNavigationDelegate extends SuggestionsNavigationDelegate {
+    private final Supplier<Tab> mParentTabSupplier;
+    private final TabDelegate mTabDelegate;
+
+    /**
+     * Creates a new {@link MostVisitedTileNavigationDelegate}.
+     * @param activity The Android activity.
+     * @param profile The currently applicable profile.
+     * @param parentTabSupplier Supplies the StartSurface's parent tab.
+     */
+    public MostVisitedTileNavigationDelegate(
+            Activity activity, Profile profile, Supplier<Tab> parentTabSupplier) {
+        super(activity, profile, /*host=*/null, /*tabModelSelector=*/null, /*tab=*/null);
+        mParentTabSupplier = parentTabSupplier;
+        mTabDelegate = new TabDelegate(false);
+    }
+
+    @Override
+    public boolean isOpenInNewTabInGroupEnabled() {
+        return false;
+    }
+
+    /**
+     * Opens the suggestions page without recording metrics.
+     *
+     * @param windowOpenDisposition How to open (new window, current tab, etc).
+     * @param url The url to navigate to.
+     */
+    @Override
+    public void navigateToSuggestionUrl(int windowOpenDisposition, String url, boolean inGroup) {
+        assert !inGroup;
+        switch (windowOpenDisposition) {
+            case WindowOpenDisposition.CURRENT_TAB:
+            case WindowOpenDisposition.NEW_BACKGROUND_TAB:
+                ReturnToChromeExperimentsUtil.handleLoadUrlFromStartSurface(
+                        new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK),
+                        windowOpenDisposition
+                                == org.chromium.ui.mojom.WindowOpenDisposition.NEW_BACKGROUND_TAB,
+                        /*incognito=*/null, mParentTabSupplier.get());
+                SuggestionsMetrics.recordTileTapped();
+                break;
+            case WindowOpenDisposition.OFF_THE_RECORD:
+                ReturnToChromeExperimentsUtil.handleLoadUrlFromStartSurface(
+                        new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK), true /*incognito*/,
+                        mParentTabSupplier.get());
+                break;
+            case WindowOpenDisposition.NEW_WINDOW:
+                openUrlInNewWindow(new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK));
+                break;
+            case WindowOpenDisposition.SAVE_TO_DISK:
+                // TODO(crbug.com/1202321): Downloading toast is not shown maybe due to the
+                // webContent is null for start surface.
+                saveUrlForOffline(url);
+                break;
+            default:
+                assert false;
+        }
+    }
+
+    private void saveUrlForOffline(String url) {
+        // TODO(crbug.com/1193816): Namespace shouldn't be NTP_SUGGESTIONS_NAMESPACE since it's
+        // not on NTP.
+        RequestCoordinatorBridge.getForProfile(Profile.getLastUsedRegularProfile())
+                .savePageLater(
+                        url, OfflinePageBridge.NTP_SUGGESTIONS_NAMESPACE, true /* userRequested */);
+    }
+
+    private void openUrlInNewWindow(LoadUrlParams loadUrlParams) {
+        mTabDelegate.createTabInOtherWindow(loadUrlParams, mActivity,
+                mParentTabSupplier.get() == null ? -1 : mParentTabSupplier.get().getId());
+    }
+}
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index 1c4817d2e..c2aba64 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -6,9 +6,9 @@
     "//chrome/android/features/start_surface/public/start_surface_public_java_sources.gni")
 
 public_tab_management_java_sources = [
-  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListProperties.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurface.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java",
+  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/CloseAllTabsDialog.java",
@@ -60,7 +60,6 @@
 ]
 
 tab_management_junit_java_sources = [
-  "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherMediatorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java",
diff --git a/chrome/android/java/res/layout/bookmark_save_flow.xml b/chrome/android/java/res/layout/bookmark_save_flow.xml
index 03d0de2..e46ef5b61 100644
--- a/chrome/android/java/res/layout/bookmark_save_flow.xml
+++ b/chrome/android/java/res/layout/bookmark_save_flow.xml
@@ -90,14 +90,16 @@
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginEnd="16dp"
-          android:layout_gravity="center_vertical" />
+          android:layout_gravity="center_vertical"
+          android:importantForAccessibility="no" />
 
       <LinearLayout
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:layout_gravity="center_vertical"
-          android:orientation="vertical">
+          android:orientation="vertical"
+          android:focusable="true">
         <TextView
             android:id="@+id/notification_switch_title"
             android:text="@string/enable_price_tracking_menu_item"
diff --git a/chrome/android/java/res/layout/mv_tiles_layout.xml b/chrome/android/java/res/layout/mv_tiles_layout.xml
new file mode 100644
index 0000000..1522daa
--- /dev/null
+++ b/chrome/android/java/res/layout/mv_tiles_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!-- A site suggestion tile. -->
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/mv_tiles_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:visibility="gone"
+    android:scrollbars="none"
+    app:layout_scrollFlags="scroll">
+
+  <org.chromium.chrome.browser.suggestions.tile.MvTilesLayout
+      android:id="@+id/mv_tiles_layout"
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:orientation="horizontal">
+  </org.chromium.chrome.browser.suggestions.tile.MvTilesLayout>
+</HorizontalScrollView>
+
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 8adcebf7..8010ce0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -959,6 +959,7 @@
         mLocaleManager.stopObservingPhoneChanges();
 
         NavigationPredictorBridge.onPause();
+        StartSurfaceUserData.getInstance().setUnusedTabRestoredAtStartup(false);
 
         super.onPauseWithNative();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 744df5e..2a82493 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -110,6 +110,7 @@
                 add(ChromeFeatureList.TAB_GROUPS_FOR_TABLETS);
                 add(ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID);
                 add(ChromeFeatureList.TAB_TO_GTS_ANIMATION);
+                add(ChromeFeatureList.TAB_STRIP_IMPROVEMENTS);
                 add(ChromeFeatureList.THEME_REFACTOR_ANDROID);
                 add(ChromeFeatureList.TOOLBAR_USE_HARDWARE_BITMAP_DRAW);
                 add(ChromeFeatureList.USE_CHIME_ANDROID_SDK);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
index 19309b1..32a5093 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowCoordinator.java
@@ -13,7 +13,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.CallbackController;
 import org.chromium.base.lifetime.DestroyChecker;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.PostTask;
@@ -26,6 +25,7 @@
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
@@ -42,18 +42,15 @@
     private final PropertyModelChangeProcessor<PropertyModel, ViewLookupCachingFrameLayout,
             PropertyKey> mChangeProcessor;
     private final DestroyChecker mDestroyChecker;
+    private final BottomSheetObserver mSheetObserver;
 
-    private CallbackController mCallbackController = new CallbackController();
     private BottomSheetController mBottomSheetController;
     private BookmarkSaveFlowBottomSheetContent mBottomSheetContent;
     private BookmarkSaveFlowMediator mMediator;
     private View mBookmarkSaveFlowView;
-
     private BookmarkModel mBookmarkModel;
-
-    private boolean mClosedViaRunnable;
-
     private UserEducationHelper mUserEducationHelper;
+    private boolean mClosedViaRunnable;
 
     /**
      * @param context The {@link Context} associated with this cooridnator.
@@ -68,6 +65,15 @@
             @NonNull UserEducationHelper userEducationHelper) {
         mContext = context;
         mBottomSheetController = bottomSheetController;
+        mSheetObserver = new EmptyBottomSheetObserver() {
+            @Override
+            public void onSheetContentChanged(BottomSheetContent newContent) {
+                if (newContent != mBottomSheetContent) {
+                    destroy();
+                }
+            }
+        };
+        mBottomSheetController.addObserver(mSheetObserver);
         mUserEducationHelper = userEducationHelper;
         mBookmarkModel = new BookmarkModel();
         mDestroyChecker = new DestroyChecker();
@@ -152,9 +158,8 @@
                         .build());
     }
 
-    private void close() {
-        mDestroyChecker.checkNotDestroyed();
-
+    @VisibleForTesting
+    void close() {
         mClosedViaRunnable = true;
         mBottomSheetController.hideContent(mBottomSheetContent, true);
     }
@@ -162,8 +167,7 @@
     private void setupAutodismiss() {
         if (!BookmarkFeatures.isImprovedSaveFlowAutodismissEnabled()) return;
 
-        PostTask.postDelayedTask(UiThreadTaskTraits.USER_VISIBLE,
-                mCallbackController.makeCancelable(this::close),
+        PostTask.postDelayedTask(UiThreadTaskTraits.USER_VISIBLE, this::close,
                 BookmarkFeatures.getImprovedSaveFlowAutodismissTimeMs());
     }
 
@@ -171,11 +175,12 @@
         mDestroyChecker.checkNotDestroyed();
         mDestroyChecker.destroy();
 
+        mBottomSheetController.removeObserver(mSheetObserver);
+
         // The bottom sheet was closed by a means other than one of the edit actions.
         if (mClosedViaRunnable) {
             RecordUserAction.record("MobileBookmark.SaveFlow.ClosedWithoutEditAction");
         }
-        mCallbackController.destroy();
 
         mMediator.destroy();
         mMediator = null;
@@ -212,9 +217,7 @@
         }
 
         @Override
-        public void destroy() {
-            BookmarkSaveFlowCoordinator.this.destroy();
-        }
+        public void destroy() {}
 
         @Override
         public int getPriority() {
@@ -266,4 +269,8 @@
     View getViewForTesting() {
         return mBookmarkSaveFlowView;
     }
+
+    boolean getIsDestroyedForTesting() {
+        return mDestroyChecker.isDestroyed();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
index 063203f..edef6ece 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
@@ -13,6 +13,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.PluralsRes;
 import androidx.annotation.VisibleForTesting;
+import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
 
@@ -763,7 +764,16 @@
             mPropertyModel.set(
                     MessageBannerProperties.ICON_TINT_COLOR, MessageBannerProperties.TINT_NONE);
             drawable = drawable.mutate();
-            ((AnimatedVectorDrawableCompat) drawable).start();
+            final AnimatedVectorDrawableCompat animatedDrawable =
+                    (AnimatedVectorDrawableCompat) drawable;
+            animatedDrawable.start();
+            animatedDrawable.registerAnimationCallback(new Animatable2Compat.AnimationCallback() {
+                @Override
+                public void onAnimationEnd(Drawable drawable) {
+                    if (mCurrentInfo == null || mCurrentInfo.icon != info.icon) return;
+                    animatedDrawable.start();
+                }
+            });
         }
 
         mPropertyModel.set(MessageBannerProperties.ICON, drawable);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListCoordinator.java
similarity index 62%
rename from chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListCoordinator.java
index 18e5ef5..9e2cf2b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListCoordinator.java
@@ -2,46 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks;
+package org.chromium.chrome.browser.suggestions.tile;
 
 import android.app.Activity;
 import android.view.ViewGroup;
 
-import androidx.annotation.VisibleForTesting;
-
 import org.chromium.base.Log;
-import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
-import org.chromium.chrome.browser.offlinepages.RequestCoordinatorBridge;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.suggestions.ImageFetcher;
 import org.chromium.chrome.browser.suggestions.SuggestionsConfig;
 import org.chromium.chrome.browser.suggestions.SuggestionsDependencyFactory;
-import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegateImpl;
 import org.chromium.chrome.browser.suggestions.mostvisited.MostVisitedSitesMetadataUtils;
-import org.chromium.chrome.browser.suggestions.tile.SuggestionsTileView;
-import org.chromium.chrome.browser.suggestions.tile.Tile;
-import org.chromium.chrome.browser.suggestions.tile.TileGroup;
-import org.chromium.chrome.browser.suggestions.tile.TileGroupDelegateImpl;
-import org.chromium.chrome.browser.suggestions.tile.TileRenderer;
-import org.chromium.chrome.browser.suggestions.tile.TileSectionType;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
-import org.chromium.chrome.browser.ui.native_page.NativePageHost;
-import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.DeviceFormFactor;
-import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
-import org.chromium.ui.mojom.WindowOpenDisposition;
 
 import java.io.IOException;
 import java.util.List;
@@ -51,7 +33,7 @@
  *
  * TODO(mattsimmons): Move logic and view manipulation into the mediator/viewbinder. (and add tests)
  */
-class MostVisitedListCoordinator implements TileGroup.Observer {
+public class MostVisitedListCoordinator implements TileGroup.Observer {
     private static final String TAG = "TopSites";
     public static final String CONTEXT_MENU_USER_ACTION_PREFIX = "Suggestions";
     private static final String NEW_TAB_URL_HELP = "https://support.google.com/chrome/?p=new_tab";
@@ -60,10 +42,9 @@
     // There's a limit of 12 in {@link MostVisitedSitesBridge#setObserver}.
     private static final int MAX_RESULTS = 12;
     private final Activity mActivity;
-    private WindowAndroid mWindowAndroid;
+    private final WindowAndroid mWindowAndroid;
     private final MvTilesLayout mMvTilesLayout;
     private final PropertyModelChangeProcessor mModelChangeProcessor;
-    private final Supplier<Tab> mParentTabSupplier;
     private final SnackbarManager mSnackbarManager;
     private TileGroup mTileGroup;
     private TileGroup.Delegate mTileGroupDelegate;
@@ -76,14 +57,13 @@
     private ImageFetcher mImageFetcher;
 
     public MostVisitedListCoordinator(Activity activity, MvTilesLayout mvTilesLayout,
-            PropertyModel propertyModel, Supplier<Tab> parentTabSupplier,
-            SnackbarManager snackbarManager, WindowAndroid windowAndroid) {
+            PropertyModel propertyModel, SnackbarManager snackbarManager,
+            WindowAndroid windowAndroid) {
         mActivity = activity;
         mWindowAndroid = windowAndroid;
         mMvTilesLayout = mvTilesLayout;
         mModelChangeProcessor = PropertyModelChangeProcessor.create(
                 propertyModel, mMvTilesLayout, MostVisitedListViewBinder::bind);
-        mParentTabSupplier = parentTabSupplier;
         mSnackbarManager = snackbarManager;
     }
 
@@ -94,7 +74,6 @@
         // If it's a cold start and Instant Start is turned on, we render MV tiles placeholder here
         // pre-native.
         if (!mInitializationComplete
-                && ReturnToChromeExperimentsUtil.isStartSurfaceEnabled(mActivity)
                 && TabUiFeatureUtilities.supportInstantStart(
                         DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity), mActivity)) {
             try {
@@ -115,8 +94,9 @@
      * Called before the TasksSurface is showing to initialize MV tiles.
      * {@link MostVisitedListCoordinator#destroyMVTiles()} is called after the TasksSurface hides.
      */
-    public void initWithNative() {
+    public void initWithNative(SuggestionsNavigationDelegate navigationDelegate) {
         Profile profile = Profile.getLastUsedRegularProfile();
+        mNavigationDelegate = navigationDelegate;
         mImageFetcher = new ImageFetcher(profile);
         if (mRenderer == null) {
             // This function is never called in incognito mode.
@@ -125,8 +105,6 @@
         } else {
             mRenderer.setImageFetcher(mImageFetcher);
         }
-        mNavigationDelegate = new MostVisitedTileNavigationDelegate(
-                mActivity, profile, null, null, null, mParentTabSupplier);
         mSuggestionsUiDelegate = new MostVisitedSuggestionsUiDelegate(
                 mNavigationDelegate, profile, mSnackbarManager);
         Runnable closeContextMenuCallback = mActivity::closeContextMenu;
@@ -186,74 +164,6 @@
         updateOfflineBadge(tile);
     }
 
-    private static class MostVisitedTileNavigationDelegate extends SuggestionsNavigationDelegate {
-        private Supplier<Tab> mParentTabSupplier;
-        private TabDelegate mTabDelegate;
-
-        public MostVisitedTileNavigationDelegate(Activity activity, Profile profile,
-                NativePageHost host, TabModelSelector tabModelSelector, Tab tab,
-                Supplier<Tab> parentTabSupplier) {
-            super(activity, profile, host, tabModelSelector, tab);
-            mParentTabSupplier = parentTabSupplier;
-            mTabDelegate = new TabDelegate(false);
-        }
-
-        @Override
-        public boolean isOpenInNewTabInGroupEnabled() {
-            return false;
-        }
-
-        /**
-         * Opens the suggestions page without recording metrics.
-         *
-         * @param windowOpenDisposition How to open (new window, current tab, etc).
-         * @param url The url to navigate to.
-         */
-        @Override
-        public void navigateToSuggestionUrl(
-                int windowOpenDisposition, String url, boolean inGroup) {
-            assert !inGroup;
-            switch (windowOpenDisposition) {
-                case WindowOpenDisposition.CURRENT_TAB:
-                case WindowOpenDisposition.NEW_BACKGROUND_TAB:
-                    ReturnToChromeExperimentsUtil.handleLoadUrlFromStartSurface(
-                            new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK),
-                            windowOpenDisposition == WindowOpenDisposition.NEW_BACKGROUND_TAB,
-                            /*incognito=*/null, mParentTabSupplier.get());
-                    SuggestionsMetrics.recordTileTapped();
-                    break;
-                case WindowOpenDisposition.OFF_THE_RECORD:
-                    ReturnToChromeExperimentsUtil.handleLoadUrlFromStartSurface(
-                            new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK),
-                            true /*incognito*/, mParentTabSupplier.get());
-                    break;
-                case WindowOpenDisposition.NEW_WINDOW:
-                    openUrlInNewWindow(new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK));
-                    break;
-                case WindowOpenDisposition.SAVE_TO_DISK:
-                    // TODO(crbug.com/1202321): Downloading toast is not shown maybe due to the
-                    // webContent is null for start surface.
-                    saveUrlForOffline(url);
-                    break;
-                default:
-                    assert false;
-            }
-        }
-
-        private void saveUrlForOffline(String url) {
-            // TODO(crbug.com/1193816): Namespace shouldn't be NTP_SUGGESTIONS_NAMESPACE since it's
-            // not on NTP.
-            RequestCoordinatorBridge.getForProfile(Profile.getLastUsedRegularProfile())
-                    .savePageLater(url, OfflinePageBridge.NTP_SUGGESTIONS_NAMESPACE,
-                            true /* userRequested */);
-        }
-
-        private void openUrlInNewWindow(LoadUrlParams loadUrlParams) {
-            mTabDelegate.createTabInOtherWindow(loadUrlParams, mActivity,
-                    mParentTabSupplier.get() == null ? -1 : mParentTabSupplier.get().getId());
-        }
-    }
-
     /** Called when the TasksSurface is hidden. */
     public void destroyMVTiles() {
         mMvTilesLayout.destroy();
@@ -284,8 +194,7 @@
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    boolean isMVTilesCleanedUp() {
+    public boolean isMVTilesCleanedUp() {
         return mTileGroupDelegate == null && mTileGroup == null;
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListProperties.java
similarity index 85%
rename from chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListProperties.java
rename to chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListProperties.java
index b5b9af5..c2bce81 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListProperties.java
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks;
+package org.chromium.chrome.browser.suggestions.tile;
 
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /** View Properties related to displaying a most visited list. */
-final class MostVisitedListProperties {
+public final class MostVisitedListProperties {
     private MostVisitedListProperties() {}
 
     public static final PropertyModel.WritableBooleanPropertyKey IS_VISIBLE =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinder.java
similarity index 81%
rename from chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinder.java
rename to chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinder.java
index fdc2793..22cf2597 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinder.java
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks;
+package org.chromium.chrome.browser.suggestions.tile;
 
-import static org.chromium.chrome.browser.tasks.MostVisitedListProperties.IS_VISIBLE;
+import static org.chromium.chrome.browser.suggestions.tile.MostVisitedListProperties.IS_VISIBLE;
 
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MvTilesLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MvTilesLayout.java
similarity index 96%
rename from chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MvTilesLayout.java
rename to chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MvTilesLayout.java
index 1fbb90cc..a12cfe5 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MvTilesLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MvTilesLayout.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks;
+package org.chromium.chrome.browser.suggestions.tile;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -14,8 +14,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
-import org.chromium.chrome.browser.suggestions.tile.SuggestionsTileView;
-import org.chromium.chrome.browser.suggestions.tile.Tile;
 import org.chromium.components.browser_ui.widget.tile.TileView;
 
 /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowTest.java
index 80753a32..9e28516 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowTest.java
@@ -28,6 +28,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -133,6 +134,21 @@
 
     @Test
     @MediumTest
+    public void testBookmarkSaveFlow_DestroyAfterHidden() throws IOException {
+        TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            BookmarkId id = addBookmark("Test bookmark", new GURL("http://a.com"));
+            mBookmarkSaveFlowCoordinator.show(id);
+            mBookmarkSaveFlowCoordinator.close();
+            return null;
+        });
+        CriteriaHelper.pollUiThread(
+                ()
+                        -> mBookmarkSaveFlowCoordinator.getIsDestroyedForTesting(),
+                "Save flow coordinator not destroyed.");
+    }
+
+    @Test
+    @MediumTest
     @Feature({"RenderTest"})
     public void testBookmarkSaveFlow_BookmarkMoved() throws IOException {
         TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
index 5b0d102a..1b62746a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
@@ -28,7 +28,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.R;
@@ -139,7 +138,6 @@
 
     @Test
     @SmallTest
-    @FlakyTest(message = "crbug.com/1291515")
     public void testRefocusing() {
         for (int i = 0; i < 5; i++) {
             mOmnibox.requestFocus();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinderUnitTest.java
similarity index 89%
rename from chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinderUnitTest.java
index af5f43b..71b6f4e 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedListViewBinderUnitTest.java
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks;
+package org.chromium.chrome.browser.suggestions.tile;
 
 import static org.mockito.Mockito.verify;
 
-import static org.chromium.chrome.browser.tasks.MostVisitedListProperties.IS_VISIBLE;
+import static org.chromium.chrome.browser.suggestions.tile.MostVisitedListProperties.IS_VISIBLE;
 
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a38a1d4..c11476ed 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2319,6 +2319,7 @@
      kDrawPredictedPointExperiment2Points3Ms,
      base::size(kDrawPredictedPointExperiment2Points3Ms), nullptr}};
 
+#if BUILDFLAG(IS_ANDROID)
 const FeatureEntry::FeatureParam kFedCmVariationAutoSignin[] = {
     {features::kFedCmAutoSigninFieldTrialParamName, "true"}};
 const FeatureEntry::FeatureParam kFedCmVariationInterception[] = {
@@ -2329,6 +2330,7 @@
     {"- with FedCM HTTP filtering (very experimental)",
      kFedCmVariationInterception, base::size(kFedCmVariationInterception),
      nullptr}};
+#endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 const FeatureEntry::Choice kForceControlFaceAeChoices[] = {
@@ -5269,6 +5271,11 @@
      flag_descriptions::kTabEngagementReportingDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kTabEngagementReportingAndroid)},
 
+    {"enable-tab-strip-improvements",
+     flag_descriptions::kTabStripImprovementsAndroidName,
+     flag_descriptions::kTabStripImprovementsAndroidDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kTabStripImprovements)},
+
     {"enable-conditional-tabstrip",
      flag_descriptions::kConditionalTabStripAndroidName,
      flag_descriptions::kConditionalTabStripAndroidDescription, kOsAndroid,
@@ -7099,11 +7106,13 @@
      flag_descriptions::kMediaSessionWebRTCDescription, kOsAll,
      FEATURE_VALUE_TYPE(media::kMediaSessionWebRTC)},
 
+#if BUILDFLAG(IS_ANDROID)
     {"fedcm", flag_descriptions::kFedCmName,
-     flag_descriptions::kFedCmDescription, kOsAll,
+     flag_descriptions::kFedCmDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(features::kFedCm,
                                     kFedCmFeatureVariations,
                                     "FedCmFeatureVariations")},
+#endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"bluetooth-sessionized-metrics",
diff --git a/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc b/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
index 1c15c8d..b35e276 100644
--- a/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api_chromeos.cc
@@ -802,12 +802,21 @@
       break;
   }
 
-  // Extract text.
+  // Extract text and hints.
   absl::optional<std::u16string> text;
+  absl::optional<std::vector<std::string>> hints;
   if (properties.text)
     text = base::UTF8ToUTF16(*properties.text);
+  // TODO(crbug.com/1252037): Convert string message IDs into ints. Then plumb
+  // vector<int> through and retrieve localized strings when populating
+  // the DictationBubbleView.
+  if (properties.hints)
+    hints = *properties.hints;
+
+  if (hints.has_value() && hints.value().size() > 5)
+    return RespondNow(Error("Should not provide more than five hints."));
 
   ash::AccessibilityController::Get()->UpdateDictationBubble(properties.visible,
-                                                             icon, text);
+                                                             icon, text, hints);
   return RespondNow(NoArguments());
 }
diff --git a/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc b/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
index 013554f26..8c8171f7 100644
--- a/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
+++ b/chrome/browser/ash/accessibility/accessibility_extension_api_browsertest.cc
@@ -49,51 +49,37 @@
     return RunExtensionTest("accessibility_private", {.custom_arg = subtest});
   }
 
-  bool IsDictationBubbleVisible() {
+  DictationBubbleController* GetDictationBubbleController() {
     DictationBubbleController* controller =
         Shell::Get()
             ->accessibility_controller()
             ->GetDictationBubbleControllerForTest();
     DCHECK(controller != nullptr);
-    return controller->widget_->IsVisible();
+    return controller;
+  }
+
+  bool IsDictationBubbleVisible() {
+    return GetDictationBubbleController()->widget_->IsVisible();
   }
 
   std::u16string GetDictationBubbleText() {
-    DictationBubbleController* controller =
-        Shell::Get()
-            ->accessibility_controller()
-            ->GetDictationBubbleControllerForTest();
-    DCHECK(controller != nullptr);
-    return controller->dictation_bubble_view_->GetTextForTesting();
+    return GetDictationBubbleController()
+        ->dictation_bubble_view_->GetTextForTesting();
   }
 
   bool IsDictationBubbleStandbyViewVisible() {
-    DictationBubbleController* controller =
-        Shell::Get()
-            ->accessibility_controller()
-            ->GetDictationBubbleControllerForTest();
-    DCHECK(controller != nullptr);
-    return controller->dictation_bubble_view_->IsStandbyViewVisibleForTesting();
+    return GetDictationBubbleController()
+        ->dictation_bubble_view_->IsStandbyViewVisibleForTesting();
   }
 
   bool IsDictationBubbleMacroSucceededImageVisible() {
-    DictationBubbleController* controller =
-        Shell::Get()
-            ->accessibility_controller()
-            ->GetDictationBubbleControllerForTest();
-    DCHECK(controller != nullptr);
-    return controller->dictation_bubble_view_
-        ->IsMacroSucceededImageVisibleForTesting();
+    return GetDictationBubbleController()
+        ->dictation_bubble_view_->IsMacroSucceededImageVisibleForTesting();
   }
 
   bool IsDictationBubbleMacroFailedImageVisible() {
-    DictationBubbleController* controller =
-        Shell::Get()
-            ->accessibility_controller()
-            ->GetDictationBubbleControllerForTest();
-    DCHECK(controller != nullptr);
-    return controller->dictation_bubble_view_
-        ->IsMacroFailedImageVisibleForTesting();
+    return GetDictationBubbleController()
+        ->dictation_bubble_view_->IsMacroFailedImageVisibleForTesting();
   }
 
   DictationBubbleIconType GetDictationBubbleVisibleIcon() {
@@ -110,6 +96,22 @@
     return DictationBubbleIconType::kHidden;
   }
 
+  bool DictationBubbleHasVisibleHints(
+      const std::vector<std::u16string>& expected) {
+    std::vector<std::u16string> actual =
+        GetDictationBubbleController()
+            ->dictation_bubble_view_->GetVisibleHintsForTesting();
+    if (expected.size() != actual.size())
+      return false;
+
+    for (size_t i = 0; i < expected.size(); ++i) {
+      if (expected[i] != actual[i])
+        return false;
+    }
+
+    return true;
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
@@ -313,6 +315,28 @@
   ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
 }
 
+IN_PROC_BROWSER_TEST_P(AccessibilityPrivateApiTest,
+                       UpdateDictationBubbleWithHints) {
+  Shell::Get()->accessibility_controller()->dictation().SetEnabled(true);
+  ExtensionTestMessageListener show_listener("Some hints", /*will_reply=*/true);
+  ExtensionTestMessageListener no_hints_listener("No hints",
+                                                 /*will_reply=*/false);
+  extensions::ResultCatcher result_catcher;
+  ASSERT_TRUE(RunSubtest("testUpdateDictationBubbleWithHints")) << message_;
+
+  ASSERT_TRUE(show_listener.WaitUntilSatisfied());
+  EXPECT_TRUE(IsDictationBubbleVisible());
+  EXPECT_TRUE(DictationBubbleHasVisibleHints(
+      std::vector<std::u16string>{u"One", u"Two", u"Three"}));
+  show_listener.Reply("Continue");
+
+  ASSERT_TRUE(no_hints_listener.WaitUntilSatisfied());
+  EXPECT_TRUE(IsDictationBubbleVisible());
+  EXPECT_TRUE(DictationBubbleHasVisibleHints(std::vector<std::u16string>()));
+
+  ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
+}
+
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
                          AccessibilityPrivateApiTest,
                          ::testing::Values(ContextType::kPersistentBackground));
diff --git a/chrome/browser/ash/arc/privacy_items/OWNERS b/chrome/browser/ash/arc/privacy_items/OWNERS
new file mode 100644
index 0000000..4545be7
--- /dev/null
+++ b/chrome/browser/ash/arc/privacy_items/OWNERS
@@ -0,0 +1 @@
+jorgegil@google.com
\ No newline at end of file
diff --git a/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.cc b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.cc
new file mode 100644
index 0000000..a9e5203
--- /dev/null
+++ b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.cc
@@ -0,0 +1,89 @@
+// Copyright 2022 The Chromium 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/ash/arc/privacy_items/arc_privacy_items_bridge.h"
+
+#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
+#include "ash/components/arc/session/arc_bridge_service.h"
+#include "base/memory/singleton.h"
+
+namespace arc {
+
+namespace {
+
+// Singleton factory for ArcPrivacyItemsBridge.
+class ArcPrivacyItemsBridgeFactory
+    : public internal::ArcBrowserContextKeyedServiceFactoryBase<
+          ArcPrivacyItemsBridge,
+          ArcPrivacyItemsBridgeFactory> {
+ public:
+  // Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
+  static constexpr const char* kName = "ArcPrivacyItemsBridgeFactory";
+
+  static ArcPrivacyItemsBridgeFactory* GetInstance() {
+    return base::Singleton<ArcPrivacyItemsBridgeFactory>::get();
+  }
+
+ private:
+  friend base::DefaultSingletonTraits<ArcPrivacyItemsBridgeFactory>;
+
+  ArcPrivacyItemsBridgeFactory() = default;
+  ~ArcPrivacyItemsBridgeFactory() override = default;
+};
+
+}  // namespace
+
+// static
+ArcPrivacyItemsBridge* ArcPrivacyItemsBridge::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return ArcPrivacyItemsBridgeFactory::GetForBrowserContext(context);
+}
+
+ArcPrivacyItemsBridge::ArcPrivacyItemsBridge(content::BrowserContext* context,
+                                             ArcBridgeService* bridge_service)
+    : arc_bridge_service_(bridge_service) {
+  DVLOG(2) << "ArcPrivacyItemsBridge::ArcPrivacyItemsBridge";
+  arc_bridge_service_->privacy_items()->SetHost(this);
+  arc_bridge_service_->privacy_items()->AddObserver(this);
+}
+
+ArcPrivacyItemsBridge::~ArcPrivacyItemsBridge() {
+  DVLOG(2) << "ArcPrivacyItemsBridge::~ArcPrivacyItemsBridge";
+  arc_bridge_service_->privacy_items()->RemoveObserver(this);
+  arc_bridge_service_->privacy_items()->SetHost(nullptr);
+}
+
+void ArcPrivacyItemsBridge::OnPrivacyItemsChanged(
+    std::vector<arc::mojom::PrivacyItemPtr> privacy_items) {
+  DVLOG(1) << "ArcPrivacyItemsBridge::OnPrivacyItemsChanged size="
+           << privacy_items.size();
+}
+
+void ArcPrivacyItemsBridge::OnMicCameraIndicatorRequirementChanged(bool flag) {
+  DVLOG(1) << "ArcPrivacyItemsBridge::OnMicCameraIndicatorRequirementChanged "
+              "required="
+           << flag;
+}
+
+void ArcPrivacyItemsBridge::OnLocationIndicatorRequirementChanged(bool flag) {
+  DVLOG(1) << "ArcPrivacyItemsBridge::OnLocationIndicatorRequirementChanged "
+              "required="
+           << flag;
+}
+
+void ArcPrivacyItemsBridge::OnStaticPrivacyIndicatorBoundsChanged(
+    int32_t display_id,
+    std::vector<gfx::Rect> bounds) {
+  DVLOG(1) << "ArcPrivacyItemsBridge::OnStaticPrivacyIndicatorBoundsChanged";
+
+  auto* instance =
+      ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->privacy_items(),
+                                  OnStaticPrivacyIndicatorBoundsChanged);
+  if (!instance)
+    return;
+
+  instance->OnStaticPrivacyIndicatorBoundsChanged(display_id, bounds);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.h b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.h
new file mode 100644
index 0000000..188aef7
--- /dev/null
+++ b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium 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_ASH_ARC_PRIVACY_ITEMS_ARC_PRIVACY_ITEMS_BRIDGE_H_
+#define CHROME_BROWSER_ASH_ARC_PRIVACY_ITEMS_ARC_PRIVACY_ITEMS_BRIDGE_H_
+
+#include "ash/components/arc/mojom/privacy_items.mojom.h"
+#include "ash/components/arc/session/connection_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace content {
+
+class BrowserContext;
+}  // namespace content
+
+namespace arc {
+
+class ArcBridgeService;
+
+class ArcPrivacyItemsBridge
+    : public KeyedService,
+      public ConnectionObserver<mojom::PrivacyItemsInstance>,
+      public mojom::PrivacyItemsHost {
+ public:
+  // Returns singleton instance for the given BrowserContext,
+  // or nullptr if the browser |context| is not allowed to use ARC.
+  static ArcPrivacyItemsBridge* GetForBrowserContext(
+      content::BrowserContext* context);
+
+  ArcPrivacyItemsBridge(content::BrowserContext* context,
+                        ArcBridgeService* bridge_service);
+
+  ArcPrivacyItemsBridge(const ArcPrivacyItemsBridge&) = delete;
+  ArcPrivacyItemsBridge& operator=(const ArcPrivacyItemsBridge&) = delete;
+
+  ~ArcPrivacyItemsBridge() override;
+
+  // PrivacyItemsHost overrides.
+  void OnPrivacyItemsChanged(
+      std::vector<arc::mojom::PrivacyItemPtr> privacy_items) override;
+  void OnMicCameraIndicatorRequirementChanged(bool flag) override;
+  void OnLocationIndicatorRequirementChanged(bool flag) override;
+
+  // PrivacyItemsInstance methods:
+  void OnStaticPrivacyIndicatorBoundsChanged(int32_t display_id,
+                                             std::vector<gfx::Rect> bounds);
+
+ private:
+  ArcBridgeService* const arc_bridge_service_;
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_ARC_PRIVACY_ITEMS_ARC_PRIVACY_ITEMS_BRIDGE_H_
diff --git a/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge_unittest.cc b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge_unittest.cc
new file mode 100644
index 0000000..9ff9ebd8
--- /dev/null
+++ b/chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium 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/ash/arc/privacy_items/arc_privacy_items_bridge.h"
+#include "ash/components/arc/session/arc_bridge_service.h"
+#include "ash/components/arc/test/connection_holder_util.h"
+#include "ash/components/arc/test/fake_privacy_items_instance.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace arc {
+
+class ArcPrivacyItemsBridgeTest : public testing::Test {
+ public:
+  ArcPrivacyItemsBridgeTest() = default;
+  ~ArcPrivacyItemsBridgeTest() override = default;
+
+  void SetUp() override {
+    arc_bridge_service_ = std::make_unique<ArcBridgeService>();
+    bridge_ = std::make_unique<ArcPrivacyItemsBridge>(
+        &testing_profile_, arc_bridge_service_.get());
+    privacy_items_instance_ = std::make_unique<arc::FakePrivacyItemsInstance>();
+    arc_bridge_service_->privacy_items()->SetInstance(
+        privacy_items_instance_.get());
+    WaitForInstanceReady(arc_bridge_service_->privacy_items());
+  }
+
+  void TearDown() override {
+    arc_bridge_service_->privacy_items()->CloseInstance(
+        privacy_items_instance_.get());
+    privacy_items_instance_.reset();
+    bridge_.reset();
+    arc_bridge_service_.reset();
+  }
+
+  ArcPrivacyItemsBridge* bridge() { return bridge_.get(); }
+  FakePrivacyItemsInstance* privacy_items_instance() {
+    return privacy_items_instance_.get();
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile testing_profile_;
+  std::unique_ptr<ArcBridgeService> arc_bridge_service_;
+  std::unique_ptr<ArcPrivacyItemsBridge> bridge_;
+  std::unique_ptr<FakePrivacyItemsInstance> privacy_items_instance_;
+};
+
+TEST_F(ArcPrivacyItemsBridgeTest, ChangingPrivacyBoundsForwardsToAndroid) {
+  std::vector<gfx::Rect> bounds;
+  bridge()->OnStaticPrivacyIndicatorBoundsChanged(5, bounds);
+  EXPECT_EQ(5, privacy_items_instance()->last_bounds_display_id());
+  EXPECT_EQ(bounds, privacy_items_instance()->last_bounds());
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index 0d18655..1069e98 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -75,6 +75,7 @@
 #include "chrome/browser/ash/arc/pip/arc_pip_bridge.h"
 #include "chrome/browser/ash/arc/policy/arc_policy_bridge.h"
 #include "chrome/browser/ash/arc/print_spooler/arc_print_spooler_bridge.h"
+#include "chrome/browser/ash/arc/privacy_items/arc_privacy_items_bridge.h"
 #include "chrome/browser/ash/arc/process/arc_process_service.h"
 #include "chrome/browser/ash/arc/screen_capture/arc_screen_capture_bridge.h"
 #include "chrome/browser/ash/arc/session/arc_demo_mode_preference_handler.h"
@@ -237,6 +238,7 @@
   ArcPowerBridge::GetForBrowserContext(profile)->SetUserIdHash(
       ash::ProfileHelper::GetUserIdHashFromProfile(profile));
   ArcPrintSpoolerBridge::GetForBrowserContext(profile);
+  ArcPrivacyItemsBridge::GetForBrowserContext(profile);
   ArcProcessService::GetForBrowserContext(profile);
   ArcPropertyBridge::GetForBrowserContext(profile);
   ArcProvisionNotificationService::GetForBrowserContext(profile);
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index ed3553d..70c3ab6 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -273,6 +273,7 @@
     "browser_version_service_ash_unittest.cc",
     "chrome_app_window_tracker_ash_unittest.cc",
     "crosapi_util_unittest.cc",
+    "dlp_ash_unittest.cc",
     "download_controller_ash_unittest.cc",
     "fake_migration_progress_tracker.h",
     "field_trial_service_ash_unittest.cc",
diff --git a/chrome/browser/ash/crosapi/dlp_ash.cc b/chrome/browser/ash/crosapi/dlp_ash.cc
index dfcc04a..c104bda 100644
--- a/chrome/browser/ash/crosapi/dlp_ash.cc
+++ b/chrome/browser/ash/crosapi/dlp_ash.cc
@@ -4,11 +4,14 @@
 
 #include "chrome/browser/ash/crosapi/dlp_ash.h"
 
+#include "ash/shell.h"
+#include "base/check.h"
 #include "base/logging.h"
 #include "chrome/browser/ash/crosapi/window_util.h"
 #include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
+#include "content/public/browser/browser_thread.h"
 
 namespace crosapi {
 
@@ -25,6 +28,8 @@
       return policy::DlpRulesManager::Level::kBlock;
     case crosapi::mojom::DlpRestrictionLevel::kAllow:
       return policy::DlpRulesManager::Level::kAllow;
+    case crosapi::mojom::DlpRestrictionLevel::kNotSet:
+      return policy::DlpRulesManager::Level::kNotSet;
   }
 }
 
@@ -50,6 +55,21 @@
   return result;
 }
 
+content::DesktopMediaID AreaToDesktopMediaID(
+    const mojom::ScreenShareAreaPtr& area) {
+  aura::Window* window = area->window_id.has_value()
+                             ? GetShellSurfaceWindow(area->window_id.value())
+                             : ash::Shell::GetPrimaryRootWindow();
+  if (!window)
+    return content::DesktopMediaID();
+
+  const content::DesktopMediaID::Type media_type =
+      area->window_id.has_value() ? content::DesktopMediaID::TYPE_WINDOW
+                                  : content::DesktopMediaID::TYPE_SCREEN;
+
+  return content::DesktopMediaID::RegisterNativeWindow(media_type, window);
+}
+
 }  // namespace
 
 DlpAsh::DlpAsh() = default;
@@ -62,6 +82,7 @@
 
 void DlpAsh::DlpRestrictionsUpdated(const std::string& window_id,
                                     mojom::DlpRestrictionSetPtr restrictions) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   aura::Window* window = GetShellSurfaceWindow(window_id);
   if (!window) {
     LOG(WARNING) << "Didn't find Lacros window with id: " << window_id;
@@ -74,4 +95,84 @@
       window, ConvertMojoToDlpContentRestrictionSet(restrictions));
 }
 
+void DlpAsh::CheckScreenShareRestriction(
+    mojom::ScreenShareAreaPtr area,
+    const std::u16string& application_title,
+    CheckScreenShareRestrictionCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const content::DesktopMediaID media_id = AreaToDesktopMediaID(area);
+  if (media_id.is_null()) {
+    std::move(callback).Run(/*allowed=*/true);
+    return;
+  }
+
+  policy::DlpContentManagerAsh* dlp_content_manager =
+      policy::DlpContentManagerAsh::Get();
+  DCHECK(dlp_content_manager);
+  dlp_content_manager->CheckScreenShareRestriction(media_id, application_title,
+                                                   std::move(callback));
+}
+
+void DlpAsh::OnScreenShareStarted(
+    const std::string& label,
+    mojom::ScreenShareAreaPtr area,
+    const ::std::u16string& application_title,
+    ::mojo::PendingRemote<mojom::StateChangeDelegate> delegate) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const content::DesktopMediaID media_id = AreaToDesktopMediaID(area);
+  if (media_id.is_null())
+    return;
+
+  mojo::RemoteSetElementId id =
+      screen_share_remote_delegates_.Add(std::move(delegate));
+  base::RepeatingCallback stop_callback = base::BindRepeating(
+      &DlpAsh::StopScreenShare, weak_ptr_factory_.GetWeakPtr(), id);
+  base::RepeatingCallback state_change_callback = base::BindRepeating(
+      &DlpAsh::ChangeScreenShareState, weak_ptr_factory_.GetWeakPtr(), id);
+
+  policy::DlpContentManagerAsh* dlp_content_manager =
+      policy::DlpContentManagerAsh::Get();
+  DCHECK(dlp_content_manager);
+  dlp_content_manager->OnScreenCaptureStarted(
+      label, {media_id}, application_title, std::move(stop_callback),
+      std::move(state_change_callback));
+}
+
+void DlpAsh::OnScreenShareStopped(const std::string& label,
+                                  mojom::ScreenShareAreaPtr area) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const content::DesktopMediaID media_id = AreaToDesktopMediaID(area);
+  if (media_id.is_null())
+    return;
+
+  policy::DlpContentManagerAsh* dlp_content_manager =
+      policy::DlpContentManagerAsh::Get();
+  DCHECK(dlp_content_manager);
+  dlp_content_manager->OnScreenCaptureStopped(label, media_id);
+}
+
+void DlpAsh::ChangeScreenShareState(
+    mojo::RemoteSetElementId id,
+    const content::DesktopMediaID& media_id,
+    blink::mojom::MediaStreamStateChange new_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!screen_share_remote_delegates_.Contains(id))
+    return;
+  switch (new_state) {
+    case blink::mojom::MediaStreamStateChange::PAUSE:
+      screen_share_remote_delegates_.Get(id)->OnPause();
+      break;
+    case blink::mojom::MediaStreamStateChange::PLAY:
+      screen_share_remote_delegates_.Get(id)->OnResume();
+      break;
+  }
+}
+
+void DlpAsh::StopScreenShare(mojo::RemoteSetElementId id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!screen_share_remote_delegates_.Contains(id))
+    return;
+  screen_share_remote_delegates_.Get(id)->OnStop();
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/dlp_ash.h b/chrome/browser/ash/crosapi/dlp_ash.h
index 7b9d212..ec27d36 100644
--- a/chrome/browser/ash/crosapi/dlp_ash.h
+++ b/chrome/browser/ash/crosapi/dlp_ash.h
@@ -6,8 +6,11 @@
 #define CHROME_BROWSER_ASH_CROSAPI_DLP_ASH_H_
 
 #include "chromeos/crosapi/mojom/dlp.mojom.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/media_stream_request.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
 
 namespace crosapi {
 
@@ -26,9 +29,33 @@
   void DlpRestrictionsUpdated(
       const std::string& window_id,
       mojom::DlpRestrictionSetPtr restrictions) override;
+  void CheckScreenShareRestriction(
+      mojom::ScreenShareAreaPtr area,
+      const std::u16string& application_title,
+      CheckScreenShareRestrictionCallback callback) override;
+  void OnScreenShareStarted(
+      const std::string& label,
+      mojom::ScreenShareAreaPtr area,
+      const ::std::u16string& application_title,
+      ::mojo::PendingRemote<mojom::StateChangeDelegate> delegate) override;
+  void OnScreenShareStopped(const std::string& label,
+                            mojom::ScreenShareAreaPtr area) override;
 
  private:
+  // Callback to pass request to change screen share state to remote.
+  void ChangeScreenShareState(mojo::RemoteSetElementId id,
+                              const content::DesktopMediaID& media_id,
+                              blink::mojom::MediaStreamStateChange new_state);
+
+  // Callback to pass request to stop screen share to remote.
+  void StopScreenShare(mojo::RemoteSetElementId id);
+
   mojo::ReceiverSet<mojom::Dlp> receivers_;
+  mojo::RemoteSet<mojom::StateChangeDelegate> screen_share_remote_delegates_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<DlpAsh> weak_ptr_factory_{this};
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/dlp_ash_unittest.cc b/chrome/browser/ash/crosapi/dlp_ash_unittest.cc
new file mode 100644
index 0000000..d45297e
--- /dev/null
+++ b/chrome/browser/ash/crosapi/dlp_ash_unittest.cc
@@ -0,0 +1,200 @@
+// Copyright 2022 The Chromium 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/ash/crosapi/dlp_ash.h"
+
+#include "ash/test/ash_test_base.h"
+#include "base/test/bind.h"
+#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
+#include "chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+
+namespace crosapi {
+
+namespace {
+
+const std::u16string kAppId = u"app_id";
+constexpr char kScreenShareLabel[] = "label";
+
+class MockStateChangeDelegate : public mojom::StateChangeDelegate {
+ public:
+  MOCK_METHOD(void, OnPause, (), (override));
+  MOCK_METHOD(void, OnResume, (), (override));
+  MOCK_METHOD(void, OnStop, (), (override));
+
+  mojo::PendingRemote<mojom::StateChangeDelegate> BindAndGetRemote() {
+    DCHECK(!receiver_.is_bound());
+    return receiver_.BindNewPipeAndPassRemote();
+  }
+
+  mojo::Receiver<mojom::StateChangeDelegate> receiver_{this};
+};
+
+}  // namespace
+
+class DlpAshTest : public ash::AshTestBase {
+ public:
+  DlpAsh* dlp_ash() { return &dlp_ash_; }
+
+ private:
+  DlpAsh dlp_ash_;
+};
+
+TEST_F(DlpAshTest, CheckScreenShareRestrictionRootWindowAllowed) {
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
+      .WillOnce([](const content::DesktopMediaID& media_id,
+                   const std::u16string& application_title,
+                   base::OnceCallback<void(bool)> callback) {
+        EXPECT_EQ(content::DesktopMediaID::TYPE_SCREEN, media_id.type);
+        EXPECT_EQ(kAppId, application_title);
+        std::move(callback).Run(/*should_proceed=*/true);
+      });
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  base::RunLoop run_loop;
+  dlp_ash()->CheckScreenShareRestriction(
+      std::move(area), kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_TRUE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+TEST_F(DlpAshTest, CheckScreenShareRestrictionRootWindowNotAllowed) {
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
+      .WillOnce([](const content::DesktopMediaID& media_id,
+                   const std::u16string& application_title,
+                   base::OnceCallback<void(bool)> callback) {
+        EXPECT_EQ(content::DesktopMediaID::TYPE_SCREEN, media_id.type);
+        EXPECT_EQ(kAppId, application_title);
+        std::move(callback).Run(/*should_proceed=*/false);
+      });
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  base::RunLoop run_loop;
+  dlp_ash()->CheckScreenShareRestriction(
+      std::move(area), kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_FALSE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+TEST_F(DlpAshTest, CheckScreenShareRestrictionInvalidWindow) {
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  area->window_id = "id";
+  base::RunLoop run_loop;
+  dlp_ash()->CheckScreenShareRestriction(
+      std::move(area), kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_TRUE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+TEST_F(DlpAshTest, ScreenShareStarted) {
+  testing::StrictMock<MockStateChangeDelegate> delegate;
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+
+  base::RepeatingClosure stop_callback;
+  content::MediaStreamUI::StateChangeCallback state_change_callback;
+  content::DesktopMediaID media_id;
+
+  EXPECT_CALL(mock_dlp_content_manager, OnScreenCaptureStarted)
+      .WillOnce(
+          [&](const std::string& label,
+              std::vector<content::DesktopMediaID> ids,
+              const std::u16string& application_title,
+              base::RepeatingClosure stop_cb,
+              content::MediaStreamUI::StateChangeCallback state_change_cb) {
+            EXPECT_EQ(kScreenShareLabel, label);
+            EXPECT_EQ(1u, ids.size());
+            EXPECT_EQ(kAppId, application_title);
+            stop_callback = std::move(stop_cb);
+            state_change_callback = std::move(state_change_cb);
+            media_id = ids[0];
+            EXPECT_EQ(content::DesktopMediaID::TYPE_SCREEN, media_id.type);
+          });
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+
+  dlp_ash()->OnScreenShareStarted(kScreenShareLabel, std::move(area), kAppId,
+                                  delegate.BindAndGetRemote());
+
+  EXPECT_CALL(delegate, OnPause).Times(1);
+  state_change_callback.Run(media_id,
+                            blink::mojom::MediaStreamStateChange::PAUSE);
+
+  EXPECT_CALL(delegate, OnResume).Times(1);
+  state_change_callback.Run(media_id,
+                            blink::mojom::MediaStreamStateChange::PLAY);
+
+  EXPECT_CALL(delegate, OnStop).Times(1);
+  stop_callback.Run();
+
+  delegate.receiver_.FlushForTesting();
+}
+
+TEST_F(DlpAshTest, ScreenShareStartedInvalidWindow) {
+  testing::StrictMock<MockStateChangeDelegate> delegate;
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  area->window_id = "id";
+  dlp_ash()->OnScreenShareStarted(kScreenShareLabel, std::move(area), kAppId,
+                                  delegate.BindAndGetRemote());
+
+  delegate.receiver_.FlushForTesting();
+}
+
+TEST_F(DlpAshTest, ScreenShareStopped) {
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+
+  EXPECT_CALL(mock_dlp_content_manager, OnScreenCaptureStopped)
+      .WillOnce([&](const std::string& label,
+                    const content::DesktopMediaID& media_id) {
+        EXPECT_EQ(kScreenShareLabel, label);
+        EXPECT_EQ(content::DesktopMediaID::TYPE_SCREEN, media_id.type);
+      });
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  dlp_ash()->OnScreenShareStopped(kScreenShareLabel, std::move(area));
+}
+
+TEST_F(DlpAshTest, ScreenShareStoppedInvalidWindow) {
+  testing::StrictMock<policy::MockDlpContentManagerAsh>
+      mock_dlp_content_manager;
+  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+      &mock_dlp_content_manager);
+
+  mojom::ScreenShareAreaPtr area = mojom::ScreenShareArea::New();
+  area->window_id = "id";
+  dlp_ash()->OnScreenShareStopped(kScreenShareLabel, std::move(area));
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
index 9bf2bde..c5632af 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
@@ -257,7 +257,8 @@
     login_logout_reporter_ = ash::reporting::LoginLogoutReporter::Create(
         managed_session_service_.get());
     user_added_removed_reporter_ =
-        std::make_unique<::reporting::UserAddedRemovedReporter>();
+        ::reporting::UserAddedRemovedReporter::Create(
+            managed_session_service_.get());
     metric_reporting_manager_ = reporting::MetricReportingManager::Create(
         managed_session_service_.get());
   }
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
index ebfd58e..03ea6a1 100644
--- a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
@@ -251,16 +251,8 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   for (const content::DesktopMediaID& id : screen_capture_ids) {
-    auto screen_share_info = std::make_unique<ScreenShareInfo>(
-        label, id, application_title, stop_callback, state_change_callback);
-    DCHECK(std::find_if(
-               running_screen_shares_.begin(), running_screen_shares_.end(),
-               [&screen_share_info](
-                   const std::unique_ptr<ScreenShareInfo>& info) -> bool {
-                 return info && *info == *screen_share_info;
-               }) == running_screen_shares_.end());
-
-    running_screen_shares_.push_back(std::move(screen_share_info));
+    AddScreenShare(label, id, application_title, stop_callback,
+                   state_change_callback);
   }
   CheckRunningScreenShares();
 }
@@ -292,122 +284,6 @@
   g_dlp_content_manager = nullptr;
 }
 
-DlpContentManagerAsh::ScreenShareInfo::ScreenShareInfo(
-    const std::string& label,
-    const content::DesktopMediaID& media_id,
-    const std::u16string& application_title,
-    base::OnceClosure stop_callback,
-    content::MediaStreamUI::StateChangeCallback state_change_callback)
-    : label_(label),
-      media_id_(media_id),
-      application_title_(application_title),
-      stop_callback_(std::move(stop_callback)),
-      state_change_callback_(std::move(state_change_callback)) {}
-
-DlpContentManagerAsh::ScreenShareInfo::~ScreenShareInfo() {
-  // Hide notifications if necessary.
-  HideNotifications();
-}
-
-bool DlpContentManagerAsh::ScreenShareInfo::operator==(
-    const DlpContentManagerAsh::ScreenShareInfo& other) const {
-  return label_ == other.label_ && media_id_ == other.media_id_;
-}
-
-bool DlpContentManagerAsh::ScreenShareInfo::operator!=(
-    const DlpContentManagerAsh::ScreenShareInfo& other) const {
-  return !(*this == other);
-}
-
-const content::DesktopMediaID&
-DlpContentManagerAsh::ScreenShareInfo::GetMediaId() const {
-  return media_id_;
-}
-
-const std::string& DlpContentManagerAsh::ScreenShareInfo::GetLabel() const {
-  return label_;
-}
-
-const std::u16string&
-DlpContentManagerAsh::ScreenShareInfo::GetApplicationTitle() const {
-  // TODO(crbug.com/1264793): Don't cache the application name, but compute it
-  // here.
-  return application_title_;
-}
-
-bool DlpContentManagerAsh::ScreenShareInfo::IsRunning() const {
-  return state_ == State::kRunning;
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::Pause() {
-  DCHECK(state_ == State::kRunning);
-  state_change_callback_.Run(media_id_,
-                             blink::mojom::MediaStreamStateChange::PAUSE);
-  state_ = State::kPaused;
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::Resume() {
-  DCHECK(state_ == State::kPaused);
-  state_change_callback_.Run(media_id_,
-                             blink::mojom::MediaStreamStateChange::PLAY);
-  state_ = State::kRunning;
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::Stop() {
-  DCHECK(state_ != State::kStopped);
-  if (stop_callback_) {
-    std::move(stop_callback_).Run();
-    state_ = State::kStopped;
-  } else {
-    NOTREACHED();
-  }
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::MaybeUpdateNotifications() {
-  UpdatePausedNotification(/*show=*/state_ == State::kPaused);
-  UpdateResumedNotification(/*show=*/state_ == State::kRunning);
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::HideNotifications() {
-  UpdatePausedNotification(/*show=*/false);
-  UpdateResumedNotification(/*show=*/false);
-}
-
-base::WeakPtr<DlpContentManagerAsh::ScreenShareInfo>
-DlpContentManagerAsh::ScreenShareInfo::GetWeakPtr() {
-  return weak_factory_.GetWeakPtr();
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::UpdatePausedNotification(
-    bool show) {
-  if ((notification_state_ == NotificationState::kShowingPausedNotification) ==
-      show)
-    return;
-  if (show) {
-    DCHECK(state_ == State::kPaused);
-    ShowDlpScreenSharePausedNotification(label_, application_title_);
-    notification_state_ = NotificationState::kShowingPausedNotification;
-  } else {
-    HideDlpScreenSharePausedNotification(label_);
-    notification_state_ = NotificationState::kNotShowingNotification;
-  }
-}
-
-void DlpContentManagerAsh::ScreenShareInfo::UpdateResumedNotification(
-    bool show) {
-  if ((notification_state_ == NotificationState::kShowingResumedNotification) ==
-      show)
-    return;
-  if (show) {
-    DCHECK(state_ == State::kRunning);
-    ShowDlpScreenShareResumedNotification(label_, application_title_);
-    notification_state_ = NotificationState::kShowingResumedNotification;
-  } else {
-    HideDlpScreenShareResumedNotification(label_);
-    notification_state_ = NotificationState::kNotShowingNotification;
-  }
-}
-
 DlpContentManagerAsh::VideoCaptureInfo::VideoCaptureInfo(
     const ScreenshotArea& area)
     : area(area) {}
@@ -676,7 +552,7 @@
   return info;
 }
 
-DlpContentManagerAsh::ConfidentialContentsInfo
+DlpContentManager::ConfidentialContentsInfo
 DlpContentManagerAsh::GetScreenShareConfidentialContentsInfo(
     const content::DesktopMediaID& media_id) const {
   if (media_id.type == content::DesktopMediaID::Type::TYPE_SCREEN) {
@@ -761,70 +637,6 @@
   }
 }
 
-void DlpContentManagerAsh::RemoveScreenShare(
-    const std::string& label,
-    const content::DesktopMediaID& media_id) {
-  base::EraseIf(
-      running_screen_shares_,
-      [=](const std::unique_ptr<ScreenShareInfo>& screen_share_info) -> bool {
-        return screen_share_info->GetLabel() == label &&
-               screen_share_info->GetMediaId() == media_id;
-      });
-}
-
-void DlpContentManagerAsh::CheckRunningScreenShares() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  for (std::unique_ptr<ScreenShareInfo>& screen_share :
-       running_screen_shares_) {
-    ConfidentialContentsInfo info =
-        GetScreenShareConfidentialContentsInfo(screen_share->GetMediaId());
-    if (IsBlocked(info.restriction_info)) {
-      if (screen_share->IsRunning()) {
-        screen_share->Pause();
-        MaybeReportEvent(info.restriction_info,
-                         DlpRulesManager::Restriction::kScreenShare);
-        DlpBooleanHistogram(dlp::kScreenSharePausedOrResumedUMA, true);
-        screen_share->MaybeUpdateNotifications();
-      }
-      continue;
-    }
-    if (is_screen_share_warning_mode_enabled_ &&
-        IsWarn(info.restriction_info)) {
-      // Check which of the contents were already allowed and don't warn for
-      // those.
-      RemoveAllowedContents(info.confidential_contents,
-                            DlpRulesManager::Restriction::kScreenShare);
-      if (info.confidential_contents.IsEmpty()) {
-        // The user already allowed all the visible content.
-        if (!screen_share->IsRunning()) {
-          screen_share->Resume();
-          screen_share->MaybeUpdateNotifications();
-        }
-        continue;
-      }
-      if (screen_share->IsRunning()) {
-        screen_share->Pause();
-        screen_share->HideNotifications();
-      }
-      // base::Unretained(this) is safe here because DlpContentManagerAsh is
-      // initialized as a singleton that's always available in the system.
-      warn_notifier_->ShowDlpScreenShareWarningDialog(
-          base::BindOnce(&DlpContentManagerAsh::OnDlpScreenShareWarnDialogReply,
-                         base::Unretained(this), info.confidential_contents,
-                         screen_share->GetWeakPtr()),
-          info.confidential_contents, screen_share->GetApplicationTitle());
-      continue;
-    }
-    // No restrictions apply, only resume if necessary.
-    if (!screen_share->IsRunning()) {
-      screen_share->Resume();
-      DlpBooleanHistogram(dlp::kScreenSharePausedOrResumedUMA, false);
-      screen_share->MaybeUpdateNotifications();
-    }
-  }
-}
-
 // static
 base::TimeDelta DlpContentManagerAsh::GetPrivacyScreenOffDelayForTesting() {
   return kPrivacyScreenOffDelay;
@@ -866,30 +678,6 @@
   std::move(callback).Run(true);
 }
 
-void DlpContentManagerAsh::OnDlpScreenShareWarnDialogReply(
-    const DlpConfidentialContents& confidential_contents,
-    base::WeakPtr<ScreenShareInfo> screen_share,
-    bool should_proceed) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (!screen_share)
-    // The screen share was stopped before the dialog was addressed, so no need
-    // to do anything.
-    return;
-
-  if (should_proceed) {
-    screen_share->Resume();
-    for (const auto& content : confidential_contents.GetContents()) {
-      user_allowed_contents_cache_.Cache(
-          content, DlpRulesManager::Restriction::kScreenShare);
-    }
-    screen_share->MaybeUpdateNotifications();
-  } else {
-    screen_share->Stop();
-    RemoveScreenShare(screen_share->GetLabel(), screen_share->GetMediaId());
-  }
-}
-
 // ScopedDlpContentManagerAshForTesting
 ScopedDlpContentManagerAshForTesting::ScopedDlpContentManagerAshForTesting(
     DlpContentManagerAsh* test_dlp_content_manager) {
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
index 7605b01..3a76769e 100644
--- a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
+++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
@@ -79,15 +79,6 @@
   virtual bool IsScreenCaptureRestricted(
       const content::DesktopMediaID& media_id);
 
-  // Checks whether screen sharing of content from the |media_id| source with
-  // application |application_name| is restricted or not advised. Depending on
-  // the result, calls |callback| and passes an indicator whether to proceed or
-  // not.
-  void CheckScreenShareRestriction(
-      const content::DesktopMediaID& media_id,
-      const std::u16string& application_title,
-      OnDlpRestrictionCheckedCallback callback) override;
-
   // Called when video capturing for |area| is started.
   void OnVideoCaptureStarted(const ScreenshotArea& area);
 
@@ -106,19 +97,20 @@
   void CheckCaptureModeInitRestriction(
       ash::OnCaptureModeDlpRestrictionChecked callback);
 
-  // Called when screen capture is started.
-  // |state_change_callback| will be called when restricted content will appear
-  // or disappear in the captured area.
+  // DlpContentManager overrides:
+  void CheckScreenShareRestriction(
+      const content::DesktopMediaID& media_id,
+      const std::u16string& application_title,
+      OnDlpRestrictionCheckedCallback callback) override;
   void OnScreenCaptureStarted(
       const std::string& label,
       std::vector<content::DesktopMediaID> screen_capture_ids,
       const std::u16string& application_title,
       base::RepeatingClosure stop_callback,
-      content::MediaStreamUI::StateChangeCallback state_change_callback);
-
-  // Called when screen capture is stopped.
+      content::MediaStreamUI::StateChangeCallback state_change_callback)
+      override;
   void OnScreenCaptureStopped(const std::string& label,
-                              const content::DesktopMediaID& media_id);
+                              const content::DesktopMediaID& media_id) override;
 
   // Called when an updated restrictions are received for Lacros window.
   void OnWindowRestrictionChanged(aura::Window* window,
@@ -139,74 +131,6 @@
   friend class DlpContentTabHelper;
   friend class MockDlpContentManagerAsh;
 
-  // Used to keep track of running screen shares.
-  class ScreenShareInfo {
-   public:
-    ScreenShareInfo(
-        const std::string& label,
-        const content::DesktopMediaID& media_id,
-        const std::u16string& application_title,
-        base::OnceClosure stop_callback,
-        content::MediaStreamUI::StateChangeCallback state_change_callback);
-    ~ScreenShareInfo();
-
-    bool operator==(const ScreenShareInfo& other) const;
-    bool operator!=(const ScreenShareInfo& other) const;
-
-    const content::DesktopMediaID& GetMediaId() const;
-    const std::string& GetLabel() const;
-    const std::u16string& GetApplicationTitle() const;
-    bool IsRunning() const;
-
-    // Pauses a running screen share.
-    // No-op if the screen share is already paused.
-    void Pause();
-    // Resumes a paused screen share.
-    // No-op if the screen share is already running.
-    void Resume();
-    // Stops the screen share. Can only be called once.
-    void Stop();
-
-    // If necessary, hides or shows the paused/resumed notification for this
-    // screen share. The notification should be updated after changing the state
-    // from running to paused, or paused to running.
-    void MaybeUpdateNotifications();
-
-    // If shown, hides both the paused and resumed notification for this screen
-    // share.
-    void HideNotifications();
-
-    base::WeakPtr<ScreenShareInfo> GetWeakPtr();
-
-   private:
-    enum class NotificationState {
-      kNotShowingNotification,
-      kShowingPausedNotification,
-      kShowingResumedNotification
-    };
-    enum class State { kRunning, kPaused, kStopped };
-    // Shows (if |show| is true) or hides (if |show| is false) paused
-    // notification for this screen share. Does nothing if the notification is
-    // already in the required state.
-    void UpdatePausedNotification(bool show);
-    // Shows (if |show| is true) or hides (if |show| is false) resumed
-    // notification for this screen share. Does nothing if the notification is
-    // already in the required state.
-    void UpdateResumedNotification(bool show);
-
-    std::string label_;
-    content::DesktopMediaID media_id_;
-    // TODO(crbug.com/1264793): Don't cache the application name.
-    std::u16string application_title_;
-    base::OnceClosure stop_callback_;
-    content::MediaStreamUI::StateChangeCallback state_change_callback_;
-    State state_ = State::kRunning;
-    NotificationState notification_state_ =
-        NotificationState::kNotShowingNotification;
-
-    base::WeakPtrFactory<ScreenShareInfo> weak_factory_{this};
-  };
-
   // Structure to keep track of a running video capture.
   struct VideoCaptureInfo {
     explicit VideoCaptureInfo(const ScreenshotArea& area);
@@ -231,6 +155,8 @@
       const DlpContentRestrictionSet& restriction_set) override;
   void OnVisibilityChanged(content::WebContents* web_contents) override;
   void RemoveFromConfidential(content::WebContents* web_contents) override;
+  ConfidentialContentsInfo GetScreenShareConfidentialContentsInfo(
+      const content::DesktopMediaID& media_id) const override;
 
   // Updates |on_screen_restrictions_| and calls
   // OnScreenRestrictionsChanged() if needed.
@@ -255,24 +181,10 @@
       const ScreenshotArea& area,
       DlpContentRestriction restriction) const;
 
-  // Returns which level, url, and information about visible confidential
-  // contents of screen share restriction that is currently enforced for
-  // |media_id|.
-  ConfidentialContentsInfo GetScreenShareConfidentialContentsInfo(
-      const content::DesktopMediaID& media_id) const;
-
   // Checks and stops the running video capture if restricted content appeared
   // in the corresponding areas.
   void CheckRunningVideoCapture();
 
-  // Removes screen share from |running_screen_shares_|.
-  void RemoveScreenShare(const std::string& label,
-                         const content::DesktopMediaID& media_id);
-
-  // Checks and stops the running screen shares if restricted content appeared
-  // in the corresponding areas.
-  void CheckRunningScreenShares();
-
   // Get the delay before switching privacy screen off.
   static base::TimeDelta GetPrivacyScreenOffDelayForTesting();
 
@@ -282,17 +194,6 @@
       ConfidentialContentsInfo info,
       ash::OnCaptureModeDlpRestrictionChecked callback);
 
-  // Called back from Screen Share warning dialogs that are shown during the
-  // screen share. Passes along the user's response, reflected in the value of
-  // |should_proceed| along to |callback| which handles continuing or cancelling
-  // the action based on this response. In case that |should_proceed| is true,
-  // also saves the |confidential_contents| that were allowed to be shared by
-  // the user to avoid future warnings.
-  void OnDlpScreenShareWarnDialogReply(
-      const DlpConfidentialContents& confidential_contents,
-      base::WeakPtr<ScreenShareInfo> screen_share,
-      bool should_proceed);
-
   // Map of window observers for the current confidential WebContents.
   base::flat_map<content::WebContents*, std::unique_ptr<DlpWindowObserver>>
       web_contents_window_observers_;
@@ -309,9 +210,6 @@
 
   // Information about the currently running video capture area if any.
   absl::optional<VideoCaptureInfo> running_video_capture_info_;
-
-  // List of the currently running screen shares.
-  std::vector<std::unique_ptr<ScreenShareInfo>> running_screen_shares_;
 };
 
 // Helper class to call SetDlpContentManagerAshForTesting and
diff --git a/chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h b/chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h
index 8f73634..34c641a 100644
--- a/chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h
+++ b/chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h
@@ -40,6 +40,18 @@
                const std::u16string& application_title,
                OnDlpRestrictionCheckedCallback callback),
               (override));
+  MOCK_METHOD(void,
+              OnScreenCaptureStarted,
+              (const std::string&,
+               std::vector<content::DesktopMediaID>,
+               const std::u16string&,
+               base::RepeatingClosure,
+               content::MediaStreamUI::StateChangeCallback),
+              (override));
+  MOCK_METHOD(void,
+              OnScreenCaptureStopped,
+              (const std::string&, const content::DesktopMediaID&),
+              (override));
 
  protected:
   void Init() override;
diff --git a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
index 4eae3c5d..9efba1a 100644
--- a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
+++ b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
@@ -4,33 +4,38 @@
 
 #include "chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h"
 
-#include "base/logging.h"
+#include <utility>
+
 #include "base/memory/ptr_util.h"
-#include "base/sequence_checker.h"
-#include "base/task/bind_post_task.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/policy/messaging_layer/proto/synced/add_remove_user_event.pb.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "components/user_manager/user_names.h"
 
 namespace reporting {
 
-UserAddedRemovedReporter::UserAddedRemovedReporter(
-    std::unique_ptr<UserEventReporterHelper> helper)
-    : helper_(std::move(helper)) {
-  ProcessRemoveUserCache();
-  managed_session_observation_.Observe(&managed_session_service_);
+// static
+std::unique_ptr<UserAddedRemovedReporter> UserAddedRemovedReporter::Create(
+    policy::ManagedSessionService* managed_session_service) {
+  return base::WrapUnique(
+      new UserAddedRemovedReporter(std::make_unique<UserEventReporterHelper>(
+                                       Destination::ADDED_REMOVED_EVENTS),
+                                   managed_session_service));
 }
 
-UserAddedRemovedReporter::UserAddedRemovedReporter()
-    : helper_(std::make_unique<UserEventReporterHelper>(
-          Destination::ADDED_REMOVED_EVENTS)) {
-  ProcessRemoveUserCache();
-  managed_session_observation_.Observe(&managed_session_service_);
+// static
+std::unique_ptr<UserAddedRemovedReporter>
+UserAddedRemovedReporter::CreateForTesting(
+    std::unique_ptr<UserEventReporterHelper> helper,
+    policy::ManagedSessionService* managed_session_service) {
+  return base::WrapUnique(
+      new UserAddedRemovedReporter(std::move(helper), managed_session_service));
 }
 
 UserAddedRemovedReporter::~UserAddedRemovedReporter() = default;
@@ -120,4 +125,12 @@
 
   user_manager->MarkReporterInitialized();
 }
+
+UserAddedRemovedReporter::UserAddedRemovedReporter(
+    std::unique_ptr<UserEventReporterHelper> helper,
+    policy::ManagedSessionService* managed_session_service)
+    : helper_(std::move(helper)) {
+  ProcessRemoveUserCache();
+  managed_session_observation_.Observe(managed_session_service);
+}
 }  // namespace reporting
diff --git a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h
index 8f65240..703f8798 100644
--- a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h
+++ b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h
@@ -5,14 +5,14 @@
 #ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_USER_ADDED_REMOVED_USER_ADDED_REMOVED_REPORTER_H_
 #define CHROME_BROWSER_ASH_POLICY_REPORTING_USER_ADDED_REMOVED_USER_ADDED_REMOVED_REPORTER_H_
 
+#include <memory>
+
 #include "base/containers/flat_map.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/ash/policy/reporting/user_event_reporter_helper.h"
 #include "chrome/browser/ash/policy/status_collector/managed_session_service.h"
-#include "chrome/browser/policy/messaging_layer/proto/synced/add_remove_user_event.pb.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/reporting/client/report_queue_provider.h"
-#include "components/reporting/proto/synced/record_constants.pb.h"
 
 namespace reporting {
 
@@ -23,11 +23,14 @@
 class UserAddedRemovedReporter
     : public policy::ManagedSessionService::Observer {
  public:
+  // For prod. Uses the default implementation of UserEventReporterHelper.
+  static std::unique_ptr<UserAddedRemovedReporter> Create(
+      policy::ManagedSessionService* managed_session_service);
+
   // For use in testing only. Allows user to pass in a test helper.
-  explicit UserAddedRemovedReporter(
-      std::unique_ptr<UserEventReporterHelper> helper);
-  // For prod. Uses the default implementation of UserEventReporterTestHelper.
-  UserAddedRemovedReporter();
+  static std::unique_ptr<UserAddedRemovedReporter> CreateForTesting(
+      std::unique_ptr<UserEventReporterHelper> helper,
+      policy::ManagedSessionService* managed_session_service);
 
   UserAddedRemovedReporter(const UserAddedRemovedReporter& other) = delete;
   UserAddedRemovedReporter& operator=(const UserAddedRemovedReporter& other) =
@@ -50,14 +53,16 @@
                      user_manager::UserRemovalReason reason) override;
 
  private:
+  UserAddedRemovedReporter(
+      std::unique_ptr<UserEventReporterHelper> helper,
+      policy::ManagedSessionService* managed_session_service);
+
   std::unique_ptr<UserEventReporterHelper> helper_;
 
   // Maps a user's email to if they are affiliated. This is needed to determine
   // if their email may be reported.
   base::flat_map<AccountId, bool> users_to_be_deleted_;
 
-  policy::ManagedSessionService managed_session_service_;
-
   base::ScopedObservation<policy::ManagedSessionService,
                           policy::ManagedSessionService::Observer>
       managed_session_observation_{this};
diff --git a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter_unittest.cc b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter_unittest.cc
index 9ae94b0..89829c2 100644
--- a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter_unittest.cc
@@ -4,17 +4,26 @@
 
 #include "chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/policy/status_collector/managed_session_service.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/policy/messaging_layer/proto/synced/add_remove_user_event.pb.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
-#include "components/policy/core/common/cloud/dm_token.h"
 #include "components/reporting/client/mock_report_queue.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace reporting {
 
@@ -81,34 +90,46 @@
     delete mock_queue_;
   }
 
-  std::unique_ptr<TestingProfile> CreateRegularProfile(
-      base::StringPiece user_email) {
+  std::unique_ptr<TestingProfile> LoginRegularProfile(
+      base::StringPiece user_email,
+      policy::ManagedSessionService* managed_session_service) {
     const AccountId account_id =
         AccountId::FromUserEmail(std::string(user_email));
     TestingProfile::Builder profile_builder;
     profile_builder.SetProfileName(account_id.GetUserEmail());
     auto profile = profile_builder.Build();
-    user_manager_->AddUser(account_id);
+    auto* const user = user_manager_->AddUser(account_id);
     user_manager_->LoginUser(account_id, true);
+    ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
+                                                                 profile.get());
+    managed_session_service->OnUserProfileLoaded(account_id);
     return profile;
   }
 
-  std::unique_ptr<TestingProfile> CreateGuestProfile() {
+  std::unique_ptr<TestingProfile> LoginGuestProfile(
+      policy::ManagedSessionService* managed_session_service) {
     TestingProfile::Builder profile_builder;
     auto profile = profile_builder.Build();
     user_manager::User* user = user_manager_->AddGuestUser();
     user_manager_->LoginUser(user->GetAccountId(), true);
+    ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
+                                                                 profile.get());
+    managed_session_service->OnUserProfileLoaded(user->GetAccountId());
     return profile;
   }
 
-  std::unique_ptr<TestingProfile> CreateKioskProfile(
-      base::StringPiece user_email) {
+  std::unique_ptr<TestingProfile> LoginKioskProfile(
+      base::StringPiece user_email,
+      policy::ManagedSessionService* managed_session_service) {
     const AccountId account_id =
         AccountId::FromUserEmail(std::string(user_email));
     TestingProfile::Builder profile_builder;
     auto profile = profile_builder.Build();
-    user_manager_->AddKioskAppUser(account_id);
+    auto* const user = user_manager_->AddKioskAppUser(account_id);
     user_manager_->LoginUser(account_id, true);
+    ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
+                                                                 profile.get());
+    managed_session_service->OnUserProfileLoaded(account_id);
     return profile;
   }
 
@@ -144,12 +165,18 @@
             record.ParseFromString(std::string(record_string));
             priority = event_priority;
           });
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ true));
 
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnLogin(profile.get());
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  LoginRegularProfile(user_email, managed_session_service.get());
 
   EXPECT_THAT(priority, testing::Eq(::reporting::Priority::IMMEDIATE));
   EXPECT_TRUE(record.has_event_timestamp_sec());
@@ -177,12 +204,17 @@
             priority = event_priority;
           });
 
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ false, /* user_new */ true));
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/false, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
 
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnLogin(profile.get());
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  LoginRegularProfile(user_email, managed_session_service.get());
 
   EXPECT_THAT(priority, testing::Eq(::reporting::Priority::IMMEDIATE));
   EXPECT_TRUE(record.has_event_timestamp_sec());
@@ -200,18 +232,22 @@
           nullptr,
           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
-
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ false,
-      /* report_user */ true, /* user_new */ true));
-
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnLogin(profile.get());
-  reporter.OnUserToBeRemoved(account_id);
-  reporter.OnUserRemoved(account_id,
-                         user_manager::UserRemovalReason::GAIA_REMOVED);
-
   EXPECT_CALL(*mock_queue, AddRecord).Times(0);
+
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/false,
+                                   /*report_user=*/true, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  auto profile = LoginRegularProfile(user_email, managed_session_service.get());
+  managed_session_service->OnUserToBeRemoved(account_id);
+  managed_session_service->OnUserRemoved(
+      account_id, user_manager::UserRemovalReason::GAIA_REMOVED);
 }
 
 TEST_F(UserAddedRemovedReporterTest, TestExistingUserLogin) {
@@ -221,15 +257,19 @@
           nullptr,
           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
-
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ false));
-
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnLogin(profile.get());
-
   EXPECT_CALL(*mock_queue, AddRecord).Times(0);
+
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/false);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  LoginRegularProfile(user_email, managed_session_service.get());
 }
 
 TEST_F(UserAddedRemovedReporterTest, TestGuestSessionLogsIn) {
@@ -238,15 +278,19 @@
           nullptr,
           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
-
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ true));
-
-  auto profile = CreateGuestProfile();
-  reporter.OnLogin(profile.get());
-
   EXPECT_CALL(*mock_queue, AddRecord).Times(0);
+
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  LoginGuestProfile(managed_session_service.get());
 }
 
 TEST_F(UserAddedRemovedReporterTest, TestKioskUserLogsIn) {
@@ -258,15 +302,19 @@
           nullptr,
           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
-
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ true));
-
-  auto profile = CreateKioskProfile(user_email);
-  reporter.OnLogin(profile.get());
-
   EXPECT_CALL(*mock_queue, AddRecord).Times(0);
+
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  LoginKioskProfile(user_email, managed_session_service.get());
 }
 
 TEST_F(UserAddedRemovedReporterTest, TestAffiliatedUserRemoval) {
@@ -290,14 +338,20 @@
             priority = event_priority;
           });
 
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ true));
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/false);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
 
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnUserToBeRemoved(account_id);
-  reporter.OnUserRemoved(account_id,
-                         user_manager::UserRemovalReason::GAIA_REMOVED);
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  auto profile = LoginRegularProfile(user_email, managed_session_service.get());
+  managed_session_service->OnUserToBeRemoved(account_id);
+  managed_session_service->OnUserRemoved(
+      account_id, user_manager::UserRemovalReason::GAIA_REMOVED);
 
   EXPECT_THAT(priority, testing::Eq(::reporting::Priority::IMMEDIATE));
   EXPECT_TRUE(record.has_event_timestamp_sec());
@@ -330,14 +384,20 @@
             priority = event_priority;
           });
 
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ false, /* user_new */ true));
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/false, /*user_new=*/false);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
 
-  auto profile = CreateRegularProfile(user_email);
-  reporter.OnUserToBeRemoved(account_id);
-  reporter.OnUserRemoved(account_id,
-                         user_manager::UserRemovalReason::GAIA_REMOVED);
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  auto profile = LoginRegularProfile(user_email, managed_session_service.get());
+  managed_session_service->OnUserToBeRemoved(account_id);
+  managed_session_service->OnUserRemoved(
+      account_id, user_manager::UserRemovalReason::GAIA_REMOVED);
 
   EXPECT_THAT(priority, testing::Eq(::reporting::Priority::IMMEDIATE));
   EXPECT_TRUE(record.has_event_timestamp_sec());
@@ -356,16 +416,22 @@
           nullptr,
           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
-
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ true, /* user_new */ true));
-  auto profile = CreateKioskProfile(user_email);
-  reporter.OnUserToBeRemoved(account_id);
-  reporter.OnUserRemoved(account_id,
-                         user_manager::UserRemovalReason::GAIA_REMOVED);
-
   EXPECT_CALL(*mock_queue, AddRecord).Times(0);
+
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/true, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
+
+  auto profile = LoginKioskProfile(user_email, managed_session_service.get());
+  managed_session_service->OnUserToBeRemoved(account_id);
+  managed_session_service->OnUserRemoved(
+      account_id, user_manager::UserRemovalReason::GAIA_REMOVED);
 }
 
 TEST_F(UserAddedRemovedReporterTest, TestRemoteRemoval) {
@@ -380,7 +446,6 @@
   auto mock_queue = weak_mock_queue_factory_->GetWeakPtr();
 
   ::reporting::UserAddedRemovedRecord record;
-  ::reporting::UserAddedRemovedRecord record_a;
   ::reporting::Priority priority;
   EXPECT_CALL(*mock_queue, AddRecord)
       .WillOnce(
@@ -391,9 +456,15 @@
             priority = event_priority;
           });
 
-  UserAddedRemovedReporter reporter(std::make_unique<TestHelper>(
-      std::move(dummy_queue), mock_queue, /* report_event */ true,
-      /* report_user */ false, /* user_new */ true));
+  auto test_helper =
+      std::make_unique<TestHelper>(std::move(dummy_queue), mock_queue,
+                                   /*report_event=*/true,
+                                   /*report_user=*/false, /*user_new=*/true);
+  auto managed_session_service =
+      std::make_unique<policy::ManagedSessionService>();
+
+  auto reporter = UserAddedRemovedReporter::CreateForTesting(
+      std::move(test_helper), managed_session_service.get());
 
   EXPECT_THAT(priority, testing::Eq(::reporting::Priority::IMMEDIATE));
   EXPECT_TRUE(record.has_event_timestamp_sec());
diff --git a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
index aee3ac6..8abf041e 100644
--- a/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
+++ b/chrome/browser/ash/quick_pair/quick_pair_browser_delegate_impl.cc
@@ -77,10 +77,8 @@
 }
 
 Profile* QuickPairBrowserDelegateImpl::GetActiveProfile() {
-  if (!user_manager::UserManager::Get()->IsUserLoggedIn()) {
-    NOTREACHED();
+  if (!user_manager::UserManager::Get()->IsUserLoggedIn())
     return nullptr;
-  }
 
   user_manager::User* active_user =
       user_manager::UserManager::Get()->GetActiveUser();
diff --git a/chrome/browser/ash/system_logs/network_health_source.cc b/chrome/browser/ash/system_logs/network_health_source.cc
index bdc4421..a34d563 100644
--- a/chrome/browser/ash/system_logs/network_health_source.cc
+++ b/chrome/browser/ash/system_logs/network_health_source.cc
@@ -51,7 +51,7 @@
              << "\n";
       output << "Signal Strength (Samples): [";
       auto& samples = net->signal_strength_stats->samples;
-      for (int i = 0; i < samples.size(); i++) {
+      for (uint16_t i = 0; i < samples.size(); i++) {
         output << base::NumberToString(samples[i]);
         if (i < samples.size() - 1)
           output << ",";
@@ -84,7 +84,7 @@
   }
 
   std::ostringstream output;
-  for (int i = 0; i < problems.size(); i++) {
+  for (uint16_t i = 0; i < problems.size(); i++) {
     output << problems[i];
     if (i != problems.size() - 1)
       output << ", ";
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
new file mode 100644
index 0000000..1d7ff0de
--- /dev/null
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
@@ -0,0 +1,31 @@
+// Copyright 2022 The Chromium 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/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h"
+
+#include "ash/public/cpp/ambient/ambient_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/prefs/pref_service.h"
+
+PersonalizationAppAmbientProviderImpl::PersonalizationAppAmbientProviderImpl(
+    content::WebUI* web_ui)
+    : profile_(Profile::FromWebUI(web_ui)) {}
+
+PersonalizationAppAmbientProviderImpl::
+    ~PersonalizationAppAmbientProviderImpl() = default;
+
+void PersonalizationAppAmbientProviderImpl::BindInterface(
+    mojo::PendingReceiver<ash::personalization_app::mojom::AmbientProvider>
+        receiver) {
+  ambient_receiver_.reset();
+  ambient_receiver_.Bind(std::move(receiver));
+}
+
+void PersonalizationAppAmbientProviderImpl::IsAmbientModeEnabled(
+    IsAmbientModeEnabledCallback callback) {
+  PrefService* pref_service = profile_->GetPrefs();
+  DCHECK(pref_service);
+  std::move(callback).Run(
+      pref_service->GetBoolean(ash::ambient::prefs::kAmbientModeEnabled));
+}
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
new file mode 100644
index 0000000..9e90db4
--- /dev/null
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Chromium 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_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_IMPL_H_
+#define CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_IMPL_H_
+
+#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
+#include "ash/webui/personalization_app/personalization_app_ambient_provider.h"
+#include "content/public/browser/web_ui.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class Profile;
+
+class PersonalizationAppAmbientProviderImpl
+    : public ash::PersonalizationAppAmbientProvider {
+ public:
+  explicit PersonalizationAppAmbientProviderImpl(content::WebUI* web_ui);
+
+  PersonalizationAppAmbientProviderImpl(
+      const PersonalizationAppAmbientProviderImpl&) = delete;
+  PersonalizationAppAmbientProviderImpl& operator=(
+      const PersonalizationAppAmbientProviderImpl&) = delete;
+
+  ~PersonalizationAppAmbientProviderImpl() override;
+
+  void BindInterface(
+      mojo::PendingReceiver<ash::personalization_app::mojom::AmbientProvider>
+          receiver) override;
+
+  // ash::personalization_app::mojom:AmbientProvider:
+  void IsAmbientModeEnabled(IsAmbientModeEnabledCallback callback) override;
+
+ private:
+  mojo::Receiver<ash::personalization_app::mojom::AmbientProvider>
+      ambient_receiver_{this};
+
+  // Pointer to profile of user that opened personalization SWA. Not owned.
+  Profile* const profile_ = nullptr;
+};
+
+#endif  // CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_IMPL_H_
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
new file mode 100644
index 0000000..087b66c
--- /dev/null
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2022 The Chromium 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/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h"
+
+#include <memory>
+
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/ambient/ambient_prefs.h"
+#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
+#include "base/callback_helpers.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_ui.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/webui/web_ui_util.h"
+
+namespace {
+
+constexpr char kFakeTestEmail[] = "fakeemail@example.com";
+
+}  // namespace
+
+class PersonalizationAppAmbientProviderImplTest : public testing::Test {
+ public:
+  PersonalizationAppAmbientProviderImplTest()
+      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+  PersonalizationAppAmbientProviderImplTest(
+      const PersonalizationAppAmbientProviderImplTest&) = delete;
+  PersonalizationAppAmbientProviderImplTest& operator=(
+      const PersonalizationAppAmbientProviderImplTest&) = delete;
+  ~PersonalizationAppAmbientProviderImplTest() override = default;
+
+ protected:
+  // testing::Test:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        ash::features::kPersonalizationHub);
+    ASSERT_TRUE(profile_manager_.SetUp());
+    profile_ = profile_manager_.CreateTestingProfile(kFakeTestEmail);
+
+    web_contents_ = content::WebContents::Create(
+        content::WebContents::CreateParams(profile_));
+    web_ui_.set_web_contents(web_contents_.get());
+
+    ambient_provider_ =
+        std::make_unique<PersonalizationAppAmbientProviderImpl>(&web_ui_);
+
+    ambient_provider_->BindInterface(
+        ambient_provider_remote_.BindNewPipeAndPassReceiver());
+  }
+
+  TestingProfile* profile() { return profile_; }
+
+  mojo::Remote<ash::personalization_app::mojom::AmbientProvider>&
+  ambient_provider_remote() {
+    return ambient_provider_remote_;
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfileManager profile_manager_;
+  content::TestWebUI web_ui_;
+  std::unique_ptr<content::WebContents> web_contents_;
+  TestingProfile* profile_;
+  mojo::Remote<ash::personalization_app::mojom::AmbientProvider>
+      ambient_provider_remote_;
+  std::unique_ptr<PersonalizationAppAmbientProviderImpl> ambient_provider_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(PersonalizationAppAmbientProviderImplTest, IsAmbientModeEnabled) {
+  PrefService* pref_service = profile()->GetPrefs();
+  EXPECT_TRUE(pref_service);
+  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, true);
+  bool called = false;
+  ambient_provider_remote()->IsAmbientModeEnabled(
+      base::BindLambdaForTesting([&called](bool enabled) {
+        called = true;
+        EXPECT_TRUE(enabled);
+      }));
+  ambient_provider_remote().FlushForTesting();
+  EXPECT_TRUE(called);
+
+  called = false;
+  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);
+  ambient_provider_remote()->IsAmbientModeEnabled(
+      base::BindLambdaForTesting([&called](bool enabled) {
+        called = true;
+        EXPECT_FALSE(enabled);
+      }));
+  ambient_provider_remote().FlushForTesting();
+  EXPECT_TRUE(called);
+}
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index e28a62d..fcbfd13 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -985,6 +985,10 @@
 
   if (ash::features::IsPersonalizationHubEnabled()) {
     RegisterWebUIControllerInterfaceBinder<
+        ash::personalization_app::mojom::AmbientProvider,
+        ash::PersonalizationAppUI>(map);
+
+    RegisterWebUIControllerInterfaceBinder<
         ash::personalization_app::mojom::ThemeProvider,
         ash::PersonalizationAppUI>(map);
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 3aec949..38acce8d 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -976,6 +976,8 @@
     "../ash/arc/print_spooler/arc_print_spooler_util.h",
     "../ash/arc/print_spooler/print_session_impl.cc",
     "../ash/arc/print_spooler/print_session_impl.h",
+    "../ash/arc/privacy_items/arc_privacy_items_bridge.cc",
+    "../ash/arc/privacy_items/arc_privacy_items_bridge.h",
     "../ash/arc/process/arc_process.cc",
     "../ash/arc/process/arc_process.h",
     "../ash/arc/process/arc_process_service.cc",
@@ -3203,6 +3205,8 @@
     "../ash/web_applications/os_settings_web_app_info.h",
     "../ash/web_applications/os_url_handler_system_web_app_info.cc",
     "../ash/web_applications/os_url_handler_system_web_app_info.h",
+    "../ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc",
+    "../ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h",
     "../ash/web_applications/personalization_app/personalization_app_info.cc",
     "../ash/web_applications/personalization_app/personalization_app_info.h",
     "../ash/web_applications/personalization_app/personalization_app_theme_provider_impl.cc",
@@ -3820,6 +3824,8 @@
     "../ash/plugin_vm/fake_plugin_vm_features.h",
     "../ash/policy/core/user_policy_test_helper.cc",
     "../ash/policy/core/user_policy_test_helper.h",
+    "../ash/policy/dlp/mock_dlp_content_manager_ash.cc",
+    "../ash/policy/dlp/mock_dlp_content_manager_ash.h",
     "../ash/policy/handlers/fake_device_name_policy_handler.cc",
     "../ash/policy/handlers/fake_device_name_policy_handler.h",
     "../ash/policy/handlers/minimum_version_policy_test_helpers.cc",
@@ -4002,6 +4008,7 @@
     "../ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc",
     "../ash/arc/pip/arc_pip_bridge_unittest.cc",
     "../ash/arc/policy/arc_policy_bridge_unittest.cc",
+    "../ash/arc/privacy_items/arc_privacy_items_bridge_unittest.cc",
     "../ash/arc/process/arc_process_unittest.cc",
     "../ash/arc/session/arc_demo_mode_preference_handler_unittest.cc",
     "../ash/arc/session/arc_play_store_enabled_preference_handler_unittest.cc",
@@ -4389,8 +4396,6 @@
     "../ash/policy/core/user_cloud_policy_token_forwarder_unittest.cc",
     "../ash/policy/dlp/dlp_content_manager_ash_unittest.cc",
     "../ash/policy/dlp/dlp_files_controller_unittest.cc",
-    "../ash/policy/dlp/mock_dlp_content_manager_ash.cc",
-    "../ash/policy/dlp/mock_dlp_content_manager_ash.h",
     "../ash/policy/enrollment/account_status_check_fetcher_unittest.cc",
     "../ash/policy/enrollment/auto_enrollment_client_impl_unittest.cc",
     "../ash/policy/enrollment/device_cloud_policy_initializer_unittest.cc",
@@ -4599,6 +4604,7 @@
     "../ash/usb/cros_usb_detector_unittest.cc",
     "../ash/web_applications/help_app/help_app_discover_tab_notification_unittest.cc",
     "../ash/web_applications/help_app/help_app_notification_controller_unittest.cc",
+    "../ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc",
     "../ash/web_applications/personalization_app/personalization_app_theme_provider_impl_unittest.cc",
     "../ash/web_applications/personalization_app/personalization_app_user_provider_impl_unittest.cc",
     "../ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc",
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc
index 284ad24..0d14eb7 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc
@@ -153,8 +153,7 @@
 
 DlpConfidentialContentsCache::DlpConfidentialContentsCache()
     : cache_size_limit_(kDefaultCacheSizeLimit),
-      task_runner_(
-          content::GetUIThreadTaskRunner(content::BrowserTaskTraits())) {}
+      task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
 
 DlpConfidentialContentsCache::~DlpConfidentialContentsCache() = default;
 
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h
index 861c0a51..c5eeb60 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h
@@ -166,7 +166,7 @@
   const size_t cache_size_limit_;
 
   // Used to evict cache entries after the timeout.
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
 };
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
index 86744970..87d5a301 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
 #include "url/gurl.h"
@@ -139,6 +140,120 @@
   warn_notifier_ = std::make_unique<DlpWarnNotifier>();
 }
 
+DlpContentManager::ScreenShareInfo::ScreenShareInfo(
+    const std::string& label,
+    const content::DesktopMediaID& media_id,
+    const std::u16string& application_title,
+    base::OnceClosure stop_callback,
+    content::MediaStreamUI::StateChangeCallback state_change_callback)
+    : label_(label),
+      media_id_(media_id),
+      application_title_(application_title),
+      stop_callback_(std::move(stop_callback)),
+      state_change_callback_(std::move(state_change_callback)) {}
+
+DlpContentManager::ScreenShareInfo::~ScreenShareInfo() {
+  // Hide notifications if necessary.
+  HideNotifications();
+}
+
+bool DlpContentManager::ScreenShareInfo::operator==(
+    const DlpContentManager::ScreenShareInfo& other) const {
+  return label_ == other.label_ && media_id_ == other.media_id_;
+}
+
+bool DlpContentManager::ScreenShareInfo::operator!=(
+    const DlpContentManager::ScreenShareInfo& other) const {
+  return !(*this == other);
+}
+
+const content::DesktopMediaID& DlpContentManager::ScreenShareInfo::GetMediaId()
+    const {
+  return media_id_;
+}
+
+const std::string& DlpContentManager::ScreenShareInfo::GetLabel() const {
+  return label_;
+}
+
+const std::u16string& DlpContentManager::ScreenShareInfo::GetApplicationTitle()
+    const {
+  // TODO(crbug.com/1264793): Don't cache the application name, but compute it
+  // here.
+  return application_title_;
+}
+
+bool DlpContentManager::ScreenShareInfo::IsRunning() const {
+  return state_ == State::kRunning;
+}
+
+void DlpContentManager::ScreenShareInfo::Pause() {
+  DCHECK(state_ == State::kRunning);
+  state_change_callback_.Run(media_id_,
+                             blink::mojom::MediaStreamStateChange::PAUSE);
+  state_ = State::kPaused;
+}
+
+void DlpContentManager::ScreenShareInfo::Resume() {
+  DCHECK(state_ == State::kPaused);
+  state_change_callback_.Run(media_id_,
+                             blink::mojom::MediaStreamStateChange::PLAY);
+  state_ = State::kRunning;
+}
+
+void DlpContentManager::ScreenShareInfo::Stop() {
+  DCHECK(state_ != State::kStopped);
+  if (stop_callback_) {
+    std::move(stop_callback_).Run();
+    state_ = State::kStopped;
+  } else {
+    NOTREACHED();
+  }
+}
+
+void DlpContentManager::ScreenShareInfo::MaybeUpdateNotifications() {
+  UpdatePausedNotification(/*show=*/state_ == State::kPaused);
+  UpdateResumedNotification(/*show=*/state_ == State::kRunning);
+}
+
+void DlpContentManager::ScreenShareInfo::HideNotifications() {
+  UpdatePausedNotification(/*show=*/false);
+  UpdateResumedNotification(/*show=*/false);
+}
+
+base::WeakPtr<DlpContentManager::ScreenShareInfo>
+DlpContentManager::ScreenShareInfo::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+void DlpContentManager::ScreenShareInfo::UpdatePausedNotification(bool show) {
+  if ((notification_state_ == NotificationState::kShowingPausedNotification) ==
+      show)
+    return;
+  if (show) {
+    DCHECK(state_ == State::kPaused);
+    ShowDlpScreenSharePausedNotification(label_, application_title_);
+    notification_state_ = NotificationState::kShowingPausedNotification;
+  } else {
+    HideDlpScreenSharePausedNotification(label_);
+    notification_state_ = NotificationState::kNotShowingNotification;
+  }
+}
+
+void DlpContentManager::ScreenShareInfo::UpdateResumedNotification(bool show) {
+  if ((notification_state_ == NotificationState::kShowingResumedNotification) ==
+      show)
+    return;
+  if (show) {
+    DCHECK(state_ == State::kRunning);
+    ShowDlpScreenShareResumedNotification(label_, application_title_);
+    notification_state_ = NotificationState::kShowingResumedNotification;
+  } else {
+    HideDlpScreenShareResumedNotification(label_);
+    notification_state_ = NotificationState::kNotShowingNotification;
+  }
+}
+
 void DlpContentManager::SetIsScreenShareWarningModeEnabledForTesting(
     bool is_enabled) {
   is_screen_share_warning_mode_enabled_ = is_enabled;
@@ -273,6 +388,110 @@
   std::move(callback).Run(true);
 }
 
+void DlpContentManager::AddScreenShare(
+    const std::string& label,
+    const content::DesktopMediaID& media_id,
+    const std::u16string& application_title,
+    base::RepeatingClosure stop_callback,
+    content::MediaStreamUI::StateChangeCallback state_change_callback) {
+  auto screen_share_info = std::make_unique<ScreenShareInfo>(
+      label, media_id, application_title, std::move(stop_callback),
+      state_change_callback);
+  DCHECK(
+      std::find_if(running_screen_shares_.begin(), running_screen_shares_.end(),
+                   [&screen_share_info](
+                       const std::unique_ptr<ScreenShareInfo>& info) -> bool {
+                     return info && *info == *screen_share_info;
+                   }) == running_screen_shares_.end());
+
+  running_screen_shares_.push_back(std::move(screen_share_info));
+}
+
+void DlpContentManager::RemoveScreenShare(
+    const std::string& label,
+    const content::DesktopMediaID& media_id) {
+  base::EraseIf(
+      running_screen_shares_,
+      [=](const std::unique_ptr<ScreenShareInfo>& screen_share_info) -> bool {
+        return screen_share_info->GetLabel() == label &&
+               screen_share_info->GetMediaId() == media_id;
+      });
+}
+
+void DlpContentManager::CheckRunningScreenShares() {
+  for (auto& screen_share : running_screen_shares_) {
+    ConfidentialContentsInfo info =
+        GetScreenShareConfidentialContentsInfo(screen_share->GetMediaId());
+    if (IsBlocked(info.restriction_info)) {
+      if (screen_share->IsRunning()) {
+        screen_share->Pause();
+        MaybeReportEvent(info.restriction_info,
+                         DlpRulesManager::Restriction::kScreenShare);
+        DlpBooleanHistogram(dlp::kScreenSharePausedOrResumedUMA, true);
+        screen_share->MaybeUpdateNotifications();
+      }
+      return;
+    }
+    if (is_screen_share_warning_mode_enabled_ &&
+        IsWarn(info.restriction_info)) {
+      // Check which of the contents were already allowed and don't warn for
+      // those.
+      RemoveAllowedContents(info.confidential_contents,
+                            DlpRulesManager::Restriction::kScreenShare);
+      if (info.confidential_contents.IsEmpty()) {
+        // The user already allowed all the visible content.
+        if (!screen_share->IsRunning()) {
+          screen_share->Resume();
+          screen_share->MaybeUpdateNotifications();
+        }
+        return;
+      }
+      if (screen_share->IsRunning()) {
+        screen_share->Pause();
+        screen_share->HideNotifications();
+      }
+      // base::Unretained(this) is safe here because DlpContentManager is
+      // initialized as a singleton that's always available in the system.
+      warn_notifier_->ShowDlpScreenShareWarningDialog(
+          base::BindOnce(&DlpContentManager::OnDlpScreenShareWarnDialogReply,
+                         base::Unretained(this), info.confidential_contents,
+                         screen_share->GetWeakPtr()),
+          info.confidential_contents, screen_share->GetApplicationTitle());
+      return;
+    }
+    // No restrictions apply, only resume if necessary.
+    if (!screen_share->IsRunning()) {
+      screen_share->Resume();
+      DlpBooleanHistogram(dlp::kScreenSharePausedOrResumedUMA, false);
+      screen_share->MaybeUpdateNotifications();
+    }
+  }
+}
+
+void DlpContentManager::OnDlpScreenShareWarnDialogReply(
+    const DlpConfidentialContents& confidential_contents,
+    base::WeakPtr<ScreenShareInfo> screen_share,
+    bool should_proceed) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!screen_share)
+    // The screen share was stopped before the dialog was addressed, so no need
+    // to do anything.
+    return;
+
+  if (should_proceed) {
+    screen_share->Resume();
+    for (const auto& content : confidential_contents.GetContents()) {
+      user_allowed_contents_cache_.Cache(
+          content, DlpRulesManager::Restriction::kScreenShare);
+    }
+    screen_share->MaybeUpdateNotifications();
+  } else {
+    screen_share->Stop();
+    RemoveScreenShare(screen_share->GetLabel(), screen_share->GetMediaId());
+  }
+}
+
 void DlpContentManager::OnDlpWarnDialogReply(
     const DlpConfidentialContents& confidential_contents,
     DlpRulesManager::Restriction restriction,
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
index d331eaa..375e1f06 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h"
+#include "content/public/browser/media_stream_request.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -63,6 +64,21 @@
       const std::u16string& application_title,
       OnDlpRestrictionCheckedCallback callback) = 0;
 
+  // Called when screen capture is started.
+  // |state_change_callback| will be called when restricted content will appear
+  // or disappear in the captured area.
+  virtual void OnScreenCaptureStarted(
+      const std::string& label,
+      std::vector<content::DesktopMediaID> screen_capture_ids,
+      const std::u16string& application_title,
+      base::RepeatingClosure stop_callback,
+      content::MediaStreamUI::StateChangeCallback state_change_callback) = 0;
+
+  // Called when screen capture is stopped.
+  virtual void OnScreenCaptureStopped(
+      const std::string& label,
+      const content::DesktopMediaID& media_id) = 0;
+
  protected:
   friend class DlpContentManagerTestHelper;
 
@@ -72,6 +88,74 @@
       std::unique_ptr<DlpWarnNotifier> warn_notifier);
   void ResetWarnNotifierForTesting();
 
+  // Used to keep track of running screen shares.
+  class ScreenShareInfo {
+   public:
+    ScreenShareInfo(
+        const std::string& label,
+        const content::DesktopMediaID& media_id,
+        const std::u16string& application_title,
+        base::OnceClosure stop_callback,
+        content::MediaStreamUI::StateChangeCallback state_change_callback);
+    ~ScreenShareInfo();
+
+    bool operator==(const ScreenShareInfo& other) const;
+    bool operator!=(const ScreenShareInfo& other) const;
+
+    const content::DesktopMediaID& GetMediaId() const;
+    const std::string& GetLabel() const;
+    const std::u16string& GetApplicationTitle() const;
+    bool IsRunning() const;
+
+    // Pauses a running screen share.
+    // No-op if the screen share is already paused.
+    void Pause();
+    // Resumes a paused screen share.
+    // No-op if the screen share is already running.
+    void Resume();
+    // Stops the screen share. Can only be called once.
+    void Stop();
+
+    // If necessary, hides or shows the paused/resumed notification for this
+    // screen share. The notification should be updated after changing the state
+    // from running to paused, or paused to running.
+    void MaybeUpdateNotifications();
+
+    // If shown, hides both the paused and resumed notification for this screen
+    // share.
+    void HideNotifications();
+
+    base::WeakPtr<ScreenShareInfo> GetWeakPtr();
+
+   private:
+    enum class NotificationState {
+      kNotShowingNotification,
+      kShowingPausedNotification,
+      kShowingResumedNotification
+    };
+    enum class State { kRunning, kPaused, kStopped };
+    // Shows (if |show| is true) or hides (if |show| is false) paused
+    // notification for this screen share. Does nothing if the notification is
+    // already in the required state.
+    void UpdatePausedNotification(bool show);
+    // Shows (if |show| is true) or hides (if |show| is false) resumed
+    // notification for this screen share. Does nothing if the notification is
+    // already in the required state.
+    void UpdateResumedNotification(bool show);
+
+    std::string label_;
+    content::DesktopMediaID media_id_;
+    // TODO(crbug.com/1264793): Don't cache the application name.
+    std::u16string application_title_;
+    base::OnceClosure stop_callback_;
+    content::MediaStreamUI::StateChangeCallback state_change_callback_;
+    State state_ = State::kRunning;
+    NotificationState notification_state_ =
+        NotificationState::kNotShowingNotification;
+
+    base::WeakPtrFactory<ScreenShareInfo> weak_factory_{this};
+  };
+
   void SetIsScreenShareWarningModeEnabledForTesting(bool is_enabled);
 
   // Structure that relates a list of confidential contents to the
@@ -127,6 +211,39 @@
                                      ConfidentialContentsInfo info,
                                      OnDlpRestrictionCheckedCallback callback);
 
+  // Returns which level, url, and information about visible confidential
+  // contents of screen share restriction that is currently enforced for
+  // |media_id|.
+  virtual ConfidentialContentsInfo GetScreenShareConfidentialContentsInfo(
+      const content::DesktopMediaID& media_id) const = 0;
+
+  // Adds screen share to be tracked in |running_screen_shares_|.
+  void AddScreenShare(
+      const std::string& label,
+      const content::DesktopMediaID& media_id,
+      const std::u16string& application_title,
+      base::RepeatingClosure stop_callback,
+      content::MediaStreamUI::StateChangeCallback state_change_callback);
+
+  // Removes screen share from |running_screen_shares_|.
+  void RemoveScreenShare(const std::string& label,
+                         const content::DesktopMediaID& media_id);
+
+  // Checks and stops the running screen shares if restricted content appeared
+  // in the corresponding areas.
+  void CheckRunningScreenShares();
+
+  // Called back from Screen Share warning dialogs that are shown during the
+  // screen share. Passes along the user's response, reflected in the value of
+  // |should_proceed| along to |callback| which handles continuing or cancelling
+  // the action based on this response. In case that |should_proceed| is true,
+  // also saves the |confidential_contents| that were allowed to be shared by
+  // the user to avoid future warnings.
+  void OnDlpScreenShareWarnDialogReply(
+      const DlpConfidentialContents& confidential_contents,
+      base::WeakPtr<ScreenShareInfo> screen_share,
+      bool should_proceed);
+
   // Called back from warning dialogs. Passes along the user's response,
   // reflected in the value of |should_proceed| along to |callback| which
   // handles continuing or cancelling the action based on this response. In case
@@ -161,6 +278,9 @@
   // TODO(crbug.com/1264803): Change to DlpConfidentialContentsCache
   DlpConfidentialContentsCache user_allowed_contents_cache_;
 
+  // List of the currently running screen shares.
+  std::vector<std::unique_ptr<ScreenShareInfo>> running_screen_shares_;
+
   raw_ptr<DlpReportingManager> reporting_manager_{nullptr};
 
   std::unique_ptr<DlpWarnNotifier> warn_notifier_;
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
index 34d7cbe7..e3cbf2be 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h"
 
+#include "base/containers/cxx20_erase.h"
 #include "chrome/browser/ui/lacros/window_utility.h"
 #include "chromeos/crosapi/mojom/dlp.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
@@ -27,8 +28,9 @@
     case DlpRulesManager::Level::kBlock:
       return crosapi::mojom::DlpRestrictionLevel::kBlock;
     case DlpRulesManager::Level::kAllow:
-    case DlpRulesManager::Level::kNotSet:
       return crosapi::mojom::DlpRestrictionLevel::kAllow;
+    case DlpRulesManager::Level::kNotSet:
+      return crosapi::mojom::DlpRestrictionLevel::kNotSet;
   }
 }
 
@@ -57,6 +59,18 @@
   return result;
 }
 
+crosapi::mojom::ScreenShareAreaPtr ConvertToScreenShareArea(
+    const content::DesktopMediaID& media_id) {
+  auto result = crosapi::mojom::ScreenShareArea::New();
+  if (media_id.type == content::DesktopMediaID::Type::TYPE_SCREEN) {
+    return result;
+  }
+  DCHECK_EQ(media_id.type, content::DesktopMediaID::Type::TYPE_WINDOW);
+  aura::Window* window = content::DesktopMediaID::GetNativeWindowById(media_id);
+  result->window_id = lacros_window_utility::GetRootWindowUniqueId(window);
+  return result;
+}
+
 }  // namespace
 
 // static
@@ -74,10 +88,121 @@
         GetScreenShareConfidentialContentsInfoForWebContents(
             media_id.web_contents_id),
         std::move(callback));
-  } else {
-    // No restrictions apply.
-    // TODO(crbug.com/1278814): Pass the check to Ash to process there.
+    return;
+  }
+  chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
+  if (!lacros_service->IsAvailable<crosapi::mojom::Dlp>()) {
+    LOG(WARNING) << "DLP mojo service not available";
     std::move(callback).Run(true);
+    return;
+  }
+
+  int dlp_mojo_version =
+      lacros_service->GetInterfaceVersion(crosapi::mojom::Dlp::Uuid_);
+  if (dlp_mojo_version < int{crosapi::mojom::Dlp::MethodMinVersions::
+                                 kCheckScreenShareRestrictionMinVersion}) {
+    LOG(WARNING) << "DLP mojo service version does not support screen share "
+                    "restrictions";
+    std::move(callback).Run(true);
+    return;
+  }
+
+  lacros_service->GetRemote<crosapi::mojom::Dlp>()->CheckScreenShareRestriction(
+      ConvertToScreenShareArea(media_id), application_title,
+      std::move(callback));
+}
+
+void DlpContentManagerLacros::OnScreenCaptureStarted(
+    const std::string& label,
+    std::vector<content::DesktopMediaID> screen_capture_ids,
+    const std::u16string& application_title,
+    base::RepeatingClosure stop_callback,
+    content::MediaStreamUI::StateChangeCallback state_change_callback) {
+  for (const content::DesktopMediaID& media_id : screen_capture_ids) {
+    if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
+      AddScreenShare(label, media_id, application_title, stop_callback,
+                     state_change_callback);
+    } else {
+      chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
+      auto delegate = std::make_unique<ScreenShareStateChangeDelegate>(
+          label, media_id, state_change_callback, stop_callback);
+      if (lacros_service->IsAvailable<crosapi::mojom::Dlp>()) {
+        lacros_service->GetRemote<crosapi::mojom::Dlp>()->OnScreenShareStarted(
+            label, ConvertToScreenShareArea(media_id), application_title,
+            delegate->BindDelegate());
+        running_remote_screen_shares_.push_back(std::move(delegate));
+      }
+    }
+  }
+  CheckRunningScreenShares();
+}
+
+void DlpContentManagerLacros::OnScreenCaptureStopped(
+    const std::string& label,
+    const content::DesktopMediaID& media_id) {
+  if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
+    RemoveScreenShare(label, media_id);
+  } else {
+    chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
+    if (lacros_service->IsAvailable<crosapi::mojom::Dlp>()) {
+      lacros_service->GetRemote<crosapi::mojom::Dlp>()->OnScreenShareStopped(
+          label, ConvertToScreenShareArea(media_id));
+    }
+    base::EraseIf(
+        running_remote_screen_shares_,
+        [=](const std::unique_ptr<
+            DlpContentManagerLacros::ScreenShareStateChangeDelegate>& delegate)
+            -> bool {
+          return delegate->label() == label && delegate->media_id() == media_id;
+        });
+  }
+}
+
+DlpContentManagerLacros::ScreenShareStateChangeDelegate::
+    ScreenShareStateChangeDelegate(
+        const std::string& label,
+        const content::DesktopMediaID& media_id,
+        content::MediaStreamUI::StateChangeCallback state_change_callback,
+        base::OnceClosure stop_callback)
+    : label_(label),
+      media_id_(media_id),
+      state_change_callback_(std::move(state_change_callback)),
+      stop_callback_(std::move(stop_callback)) {}
+
+DlpContentManagerLacros::ScreenShareStateChangeDelegate::
+    ~ScreenShareStateChangeDelegate() = default;
+
+bool DlpContentManagerLacros::ScreenShareStateChangeDelegate::operator==(
+    const DlpContentManagerLacros::ScreenShareStateChangeDelegate& other)
+    const {
+  return label_ == other.label_ && media_id_ == other.media_id_;
+}
+
+bool DlpContentManagerLacros::ScreenShareStateChangeDelegate::operator!=(
+    const DlpContentManagerLacros::ScreenShareStateChangeDelegate& other)
+    const {
+  return !(*this == other);
+}
+
+mojo::PendingRemote<crosapi::mojom::StateChangeDelegate>
+DlpContentManagerLacros::ScreenShareStateChangeDelegate::BindDelegate() {
+  return receiver_.BindNewPipeAndPassRemoteWithVersion();
+}
+
+void DlpContentManagerLacros::ScreenShareStateChangeDelegate::OnPause() {
+  state_change_callback_.Run(media_id_,
+                             blink::mojom::MediaStreamStateChange::PAUSE);
+}
+
+void DlpContentManagerLacros::ScreenShareStateChangeDelegate::OnResume() {
+  state_change_callback_.Run(media_id_,
+                             blink::mojom::MediaStreamStateChange::PLAY);
+}
+
+void DlpContentManagerLacros::ScreenShareStateChangeDelegate::OnStop() {
+  DCHECK(stop_callback_);
+  if (stop_callback_) {
+    std::move(stop_callback_).Run();
   }
 }
 
@@ -143,4 +268,15 @@
   }
 }
 
+DlpContentManager::ConfidentialContentsInfo
+DlpContentManagerLacros::GetScreenShareConfidentialContentsInfo(
+    const content::DesktopMediaID& media_id) const {
+  if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
+    return GetScreenShareConfidentialContentsInfoForWebContents(
+        media_id.web_contents_id);
+  }
+  NOTREACHED();
+  return ConfidentialContentsInfo();
+}
+
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
index 750f575e..6864375 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
@@ -9,7 +9,9 @@
 #include "base/containers/flat_set.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
+#include "chromeos/crosapi/mojom/dlp.mojom.h"
 #include "content/public/browser/desktop_media_id.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "ui/aura/window_observer.h"
 
 namespace aura {
@@ -31,19 +33,61 @@
   // There will always be a single instance created on the first access.
   static DlpContentManagerLacros* Get();
 
-  // Checks whether screen sharing of content from the |media_id| source with
-  // application |application_name| is restricted or not advised. Depending on
-  // the result, calls |callback| and passes an indicator whether to proceed or
-  // not.
+  // DlpContentManager overrides:
   void CheckScreenShareRestriction(
       const content::DesktopMediaID& media_id,
       const std::u16string& application_title,
       OnDlpRestrictionCheckedCallback callback) override;
+  void OnScreenCaptureStarted(
+      const std::string& label,
+      std::vector<content::DesktopMediaID> screen_capture_ids,
+      const std::u16string& application_title,
+      base::RepeatingClosure stop_callback,
+      content::MediaStreamUI::StateChangeCallback state_change_callback)
+      override;
+  void OnScreenCaptureStopped(const std::string& label,
+                              const content::DesktopMediaID& media_id) override;
 
  private:
   friend class DlpContentManagerTestHelper;
   friend class DlpContentObserver;
 
+  // Class that tracks connection with screen share tracking in Ash.
+  class ScreenShareStateChangeDelegate
+      : public crosapi::mojom::StateChangeDelegate {
+   public:
+    ScreenShareStateChangeDelegate(
+        const std::string& label,
+        const content::DesktopMediaID& media_id,
+        content::MediaStreamUI::StateChangeCallback state_change_callback,
+        base::OnceClosure stop_callback);
+    ScreenShareStateChangeDelegate(const ScreenShareStateChangeDelegate&) =
+        delete;
+    ScreenShareStateChangeDelegate& operator=(
+        const ScreenShareStateChangeDelegate&) = delete;
+    ~ScreenShareStateChangeDelegate() override;
+
+    bool operator==(const ScreenShareStateChangeDelegate& other) const;
+    bool operator!=(const ScreenShareStateChangeDelegate& other) const;
+
+    mojo::PendingRemote<crosapi::mojom::StateChangeDelegate> BindDelegate();
+
+    // crosapi::mojom::StateChangeDelegate overrides:
+    void OnPause() override;
+    void OnResume() override;
+    void OnStop() override;
+
+    const std::string& label() const { return label_; }
+    const content::DesktopMediaID& media_id() const { return media_id_; }
+
+   private:
+    const std::string label_;
+    const content::DesktopMediaID media_id_;
+    content::MediaStreamUI::StateChangeCallback state_change_callback_;
+    base::OnceClosure stop_callback_;
+    mojo::Receiver<crosapi::mojom::StateChangeDelegate> receiver_{this};
+  };
+
   DlpContentManagerLacros();
   ~DlpContentManagerLacros() override;
 
@@ -61,6 +105,10 @@
   // needed.
   void UpdateRestrictions(aura::Window* window);
 
+  // DlpContentManager override:
+  ConfidentialContentsInfo GetScreenShareConfidentialContentsInfo(
+      const content::DesktopMediaID& media_id) const override;
+
   // Tracks set of known confidential WebContents* for each Window*.
   base::flat_map<aura::Window*, base::flat_set<content::WebContents*>>
       window_webcontents_;
@@ -68,6 +116,10 @@
   // Tracks current restrictions applied to Window* based on visible
   // WebContents* belonging to Window*.
   base::flat_map<aura::Window*, DlpContentRestrictionSet> confidential_windows_;
+
+  // List of currently running screen shares that are tracked remotely in Ash.
+  std::vector<std::unique_ptr<ScreenShareStateChangeDelegate>>
+      running_remote_screen_shares_;
 };
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros_browsertest.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros_browsertest.cc
new file mode 100644
index 0000000..f0d564b
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros_browsertest.cc
@@ -0,0 +1,296 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h"
+
+#include "base/test/bind.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_test_helper.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chromeos/crosapi/mojom/dlp.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "content/public/test/browser_test.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const std::u16string kAppId = u"app_id";
+constexpr char kScreenShareLabel[] = "label";
+
+// TODO(crbug.com/1291609): Mock Lacros side of crosapi instead when possible.
+// This will allow these tests to be just unit_tests, not
+// lacros_chrome_browsertests.
+class MockDlpCrosapi : public crosapi::mojom::Dlp {
+ public:
+  MOCK_METHOD(void,
+              DlpRestrictionsUpdated,
+              (const std::string&, crosapi::mojom::DlpRestrictionSetPtr),
+              (override));
+  MOCK_METHOD(void,
+              CheckScreenShareRestriction,
+              (crosapi::mojom::ScreenShareAreaPtr,
+               const std::u16string&,
+               CheckScreenShareRestrictionCallback),
+              (override));
+  MOCK_METHOD(void,
+              OnScreenShareStarted,
+              (const std::string&,
+               crosapi::mojom::ScreenShareAreaPtr,
+               const ::std::u16string&,
+               ::mojo::PendingRemote<crosapi::mojom::StateChangeDelegate>),
+              (override));
+  MOCK_METHOD(void,
+              OnScreenShareStopped,
+              (const std::string&, crosapi::mojom::ScreenShareAreaPtr),
+              (override));
+};
+
+}  // namespace
+
+class DlpContentManagerLacrosBrowserTest : public InProcessBrowserTest {
+ public:
+  // InProcessBrowserTest:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+
+    // If DLP interface is not available on this version of ash-chrome, this
+    // test suite will no-op.
+    if (!IsServiceAvailable())
+      return;
+
+    test_helper_ = std::make_unique<DlpContentManagerTestHelper>();
+
+    // Replace the production DLP service with a mock for testing.
+    mojo::Remote<crosapi::mojom::Dlp>& remote =
+        chromeos::LacrosService::Get()->GetRemote<crosapi::mojom::Dlp>();
+    remote.reset();
+    receiver_.Bind(remote.BindNewPipeAndPassReceiver());
+  }
+
+  // Returns whether the DLP interface is available. It may
+  // not be available on earlier versions of ash-chrome.
+  bool IsServiceAvailable() const {
+    chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
+    return lacros_service && lacros_service->IsAvailable<crosapi::mojom::Dlp>();
+  }
+
+  void SetDlpInterfaceVersion(int version) {
+    crosapi::mojom::BrowserInitParamsPtr init_params =
+        chromeos::LacrosService::Get()->init_params()->Clone();
+    init_params->interface_versions.value()[crosapi::mojom::Dlp::Uuid_] =
+        version;
+    chromeos::LacrosService::Get()->SetInitParamsForTests(
+        std::move(init_params));
+  }
+
+  DlpContentManagerLacros* manager() {
+    return static_cast<DlpContentManagerLacros*>(
+        test_helper_->GetContentManager());
+  }
+
+  testing::StrictMock<MockDlpCrosapi>& service() { return service_; }
+
+ private:
+  testing::StrictMock<MockDlpCrosapi> service_;
+  mojo::Receiver<crosapi::mojom::Dlp> receiver_{&service_};
+  std::unique_ptr<DlpContentManagerTestHelper> test_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    DlpContentManagerLacrosBrowserTest,
+    CheckScreenShareRestrictionFullScreenNotSupportedVersion) {
+  // If DLP interface is not available on this version of ash-chrome, this test
+  // suite will no-op.
+  if (!IsServiceAvailable())
+    return;
+
+  SetDlpInterfaceVersion(0);
+
+  // No call to mojo remote should happen in this case (ensured by StrictMock).
+  // The request is just silently allowed.
+  base::RunLoop run_loop;
+  manager()->CheckScreenShareRestriction(
+      content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                              content::DesktopMediaID::kFakeId),
+      kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_TRUE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DlpContentManagerLacrosBrowserTest,
+                       CheckScreenShareRestrictionFullScreenAllowed) {
+  // If DLP interface is not available on this version of ash-chrome, this test
+  // suite will no-op.
+  if (!IsServiceAvailable())
+    return;
+
+  SetDlpInterfaceVersion(service().Version_);
+
+  EXPECT_CALL(service(), CheckScreenShareRestriction)
+      .WillOnce([](crosapi::mojom::ScreenShareAreaPtr area,
+                   const std::u16string& application_title,
+                   base::OnceCallback<void(bool)> callback) {
+        EXPECT_FALSE(area->window_id.has_value());
+        EXPECT_EQ(kAppId, application_title);
+        std::move(callback).Run(/*should_proceed=*/true);
+      });
+
+  base::RunLoop run_loop;
+  manager()->CheckScreenShareRestriction(
+      content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                              content::DesktopMediaID::kFakeId),
+      kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_TRUE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DlpContentManagerLacrosBrowserTest,
+                       CheckScreenShareRestrictionFullScreenNotAllowed) {
+  // If DLP interface is not available on this version of ash-chrome, this test
+  // suite will no-op.
+  if (!IsServiceAvailable())
+    return;
+
+  SetDlpInterfaceVersion(service().Version_);
+
+  EXPECT_CALL(service(), CheckScreenShareRestriction)
+      .WillOnce([](crosapi::mojom::ScreenShareAreaPtr area,
+                   const std::u16string& application_title,
+                   base::OnceCallback<void(bool)> callback) {
+        EXPECT_FALSE(area->window_id.has_value());
+        EXPECT_EQ(kAppId, application_title);
+        std::move(callback).Run(/*should_proceed=*/false);
+      });
+
+  base::RunLoop run_loop;
+  manager()->CheckScreenShareRestriction(
+      content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                              content::DesktopMediaID::kFakeId),
+      kAppId, base::BindLambdaForTesting([&](bool allowed) {
+        EXPECT_FALSE(allowed);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DlpContentManagerLacrosBrowserTest,
+                       ScreenShareStartedStop) {
+  // If DLP interface is not available on this version of ash-chrome, this test
+  // suite will no-op.
+  if (!IsServiceAvailable())
+    return;
+
+  SetDlpInterfaceVersion(service().Version_);
+
+  // Setup callback on remote service.
+  base::RunLoop bound_loop;
+  ::mojo::PendingRemote<crosapi::mojom::StateChangeDelegate> pending_delegate;
+  EXPECT_CALL(service(), OnScreenShareStarted)
+      .WillOnce([&](const std::string& label,
+                    crosapi::mojom::ScreenShareAreaPtr area,
+                    const std::u16string& application_title,
+                    ::mojo::PendingRemote<crosapi::mojom::StateChangeDelegate>
+                        delegate) {
+        EXPECT_EQ(kScreenShareLabel, label);
+        EXPECT_FALSE(area->window_id.has_value());
+        EXPECT_EQ(kAppId, application_title);
+        pending_delegate = std::move(delegate);
+        bound_loop.Quit();
+      });
+
+  // Call DLP manager and expect stop callback.
+  base::RunLoop stopped_run_loop;
+  manager()->OnScreenCaptureStarted(
+      kScreenShareLabel,
+      {content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                               content::DesktopMediaID::kFakeId)},
+      kAppId, base::BindLambdaForTesting([&]() { stopped_run_loop.Quit(); }),
+      base::DoNothing());
+
+  // Bind remote delegate.
+  bound_loop.Run();
+  mojo::Remote<crosapi::mojom::StateChangeDelegate> remote_delegate(
+      std::move(pending_delegate));
+  EXPECT_TRUE(remote_delegate);
+
+  // Request remote delegate to stop.
+  remote_delegate->OnStop();
+
+  // Wait for stop callback.
+  stopped_run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(DlpContentManagerLacrosBrowserTest,
+                       ScreenShareStartedPauseAndResume) {
+  // If DLP interface is not available on this version of ash-chrome, this test
+  // suite will no-op.
+  if (!IsServiceAvailable())
+    return;
+
+  SetDlpInterfaceVersion(service().Version_);
+
+  // Setup callback on remote service.
+  base::RunLoop bound_loop;
+  ::mojo::PendingRemote<crosapi::mojom::StateChangeDelegate> pending_delegate;
+  EXPECT_CALL(service(), OnScreenShareStarted)
+      .WillOnce([&](const std::string& label,
+                    crosapi::mojom::ScreenShareAreaPtr area,
+                    const std::u16string& application_title,
+                    ::mojo::PendingRemote<crosapi::mojom::StateChangeDelegate>
+                        delegate) {
+        EXPECT_EQ(kScreenShareLabel, label);
+        EXPECT_FALSE(area->window_id.has_value());
+        EXPECT_EQ(kAppId, application_title);
+        pending_delegate = std::move(delegate);
+        bound_loop.Quit();
+      });
+
+  // Call DLP manager and expect state change callbacks.
+  base::RunLoop paused_run_loop;
+  base::RunLoop resumed_run_loop;
+  content::DesktopMediaID media_id(content::DesktopMediaID::TYPE_SCREEN,
+                                   content::DesktopMediaID::kFakeId);
+  manager()->OnScreenCaptureStarted(
+      kScreenShareLabel, {media_id}, kAppId, base::DoNothing(),
+      base::BindLambdaForTesting(
+          [&](const content::DesktopMediaID& in_media_id,
+              blink::mojom::MediaStreamStateChange new_state) {
+            EXPECT_EQ(media_id, in_media_id);
+            if (new_state == blink::mojom::MediaStreamStateChange::PAUSE) {
+              paused_run_loop.Quit();
+            } else if (new_state ==
+                       blink::mojom::MediaStreamStateChange::PLAY) {
+              resumed_run_loop.Quit();
+            } else {
+              NOTREACHED();
+            }
+          }));
+
+  // Bind remote delegate.
+  bound_loop.Run();
+  mojo::Remote<crosapi::mojom::StateChangeDelegate> remote_delegate(
+      std::move(pending_delegate));
+  EXPECT_TRUE(remote_delegate);
+
+  // Request remote delegate to pause.
+  remote_delegate->OnPause();
+
+  // Wait for stop callback.
+  paused_run_loop.Run();
+
+  // Request remote delegate to resume.
+  remote_delegate->OnResume();
+
+  // Wait for stop callback.
+  resumed_run_loop.Run();
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h
index e773f8a..002ee56 100644
--- a/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h
+++ b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h
@@ -26,10 +26,26 @@
   MOCK_METHOD(void, OnVisibilityChanged, (content::WebContents*), (override));
   MOCK_METHOD(void,
               CheckScreenShareRestriction,
-              (const content::DesktopMediaID& media_id,
-               const std::u16string& application_title,
-               OnDlpRestrictionCheckedCallback callback),
+              (const content::DesktopMediaID&,
+               const std::u16string&,
+               OnDlpRestrictionCheckedCallback),
               (override));
+  MOCK_METHOD(void,
+              OnScreenCaptureStarted,
+              (const std::string&,
+               std::vector<content::DesktopMediaID>,
+               const std::u16string&,
+               base::RepeatingClosure,
+               content::MediaStreamUI::StateChangeCallback),
+              (override));
+  MOCK_METHOD(void,
+              OnScreenCaptureStopped,
+              (const std::string&, const content::DesktopMediaID&),
+              (override));
+  MOCK_METHOD(ConfidentialContentsInfo,
+              GetScreenShareConfidentialContentsInfo,
+              (const content::DesktopMediaID&),
+              (const override));
 };
 
 }  // namespace policy
diff --git a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
index 2fcead3..cdcec1c6 100644
--- a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
+++ b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
@@ -52,10 +52,6 @@
          host == chrome::kChromeUIPasswordManagerInternalsHost ||
          host == chrome::kChromeUIPolicyHost ||
          host == chrome::kChromeUIPredictorsHost ||
-         // TODO(crbug.com/1202165): Remove when new quota-internals page is
-         // done.
-         host == chrome::kChromeUIQuotaInternalsHost ||
-         host == content::kChromeUIQuotaInternals2Host ||
          host == chrome::kChromeUISandboxHost ||
          host == chrome::kChromeUISignInInternalsHost ||
          host == chrome::kChromeUISiteEngagementHost ||
@@ -74,6 +70,7 @@
          host == content::kChromeUIMediaInternalsHost ||
          host == content::kChromeUINetworkErrorsListingHost ||
          host == content::kChromeUIProcessInternalsHost ||
+         host == content::kChromeUIQuotaInternalsHost ||
          host == content::kChromeUIServiceWorkerInternalsHost ||
          host == content::kChromeUIUkmHost ||
          host == content::kChromeUIWebRTCInternalsHost;
diff --git a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
index d5c7070..ec1d33d 100644
--- a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
@@ -99,6 +99,9 @@
 #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   return "Linux";
 #endif
+#if BUILDFLAG(IS_FUCHSIA)
+  return "Fuchsia";
+#endif
 }
 
 }  // namespace
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ea15a0d..3faebe4 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2750,6 +2750,11 @@
     "expiry_milestone": 93
   },
   {
+    "name": "enable-tab-strip-improvements",
+    "owners": [ "skavuluru", "clank-app-team@google.com" ],
+    "expiry_milestone": 105
+  },
+  {
     "name": "enable-tab-switcher-on-return",
     "owners": [ "memex-team@google.com" ],
     "expiry_milestone": 100
@@ -3880,12 +3885,12 @@
   {
     "name": "messages-for-android-chrome-survey",
     "owners": [ "pavely", "aishwaryarj" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-infrastructure",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 102
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-instant-apps",
@@ -3900,17 +3905,17 @@
   {
     "name": "messages-for-android-notification-blocked",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-passwords",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-permission-update",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-popup-blocked",
@@ -3920,7 +3925,7 @@
   {
     "name": "messages-for-android-pwa-install",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 101
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-reader-mode",
@@ -3935,7 +3940,7 @@
   {
     "name": "messages-for-android-save-card",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "messages-for-android-sync-error",
@@ -3945,7 +3950,7 @@
   {
     "name": "messages-for-android-update-password",
     "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 99
+    "expiry_milestone": 105
   },
   {
     "name": "metal",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 4af113a..2dc0ed1 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3486,6 +3486,11 @@
 const char kTabGroupsForTabletsName[] = "Tab groups on tablets";
 const char kTabGroupsForTabletsDescription[] = "Enable tab groups on tablets.";
 
+const char kTabStripImprovementsAndroidName[] =
+    "Tab strip improvements for Android.";
+const char kTabStripImprovementsAndroidDescription[] =
+    "Enables scrollable tab strip with tab group indicators.";
+
 const char kThemeRefactorAndroidName[] = "Theme refactor on Android";
 const char kThemeRefactorAndroidDescription[] =
     "Enables the theme refactoring on Android.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index cbe49fb..75ebf9c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1387,6 +1387,9 @@
 extern const char kTabSearchFuzzySearchName[];
 extern const char kTabSearchFuzzySearchDescription[];
 
+extern const char kTabStripImprovementsAndroidName[];
+extern const char kTabStripImprovementsAndroidDescription[];
+
 extern const char kTailoredSecurityIntegrationName[];
 extern const char kTailoredSecurityIntegrationDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 1df9faf9..cc11dc6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -277,6 +277,7 @@
     &kTabGroupsForTablets,
     &kTabGridLayoutAndroid,
     &kTabReparenting,
+    &kTabStripImprovements,
     &kTabSwitcherOnReturn,
     &kTabToGTSAnimation,
     &kTestDefaultDisabled,
@@ -770,6 +771,9 @@
 const base::Feature kTabReparenting{"TabReparenting",
                                     base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kTabStripImprovements{"TabStripImprovements",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kTabSwitcherOnReturn{"TabSwitcherOnReturn",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 66afd848..2c769b6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -146,6 +146,7 @@
 extern const base::Feature kTabGroupsForTablets;
 extern const base::Feature kTabGridLayoutAndroid;
 extern const base::Feature kTabReparenting;
+extern const base::Feature kTabStripImprovements;
 extern const base::Feature kTabSwitcherOnReturn;
 extern const base::Feature kTabToGTSAnimation;
 extern const base::Feature kTestDefaultDisabled;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index 27e08ce..b5d3b92d 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -103,6 +103,7 @@
                     .put(ChromeFeatureList.FEED_LOADING_PLACEHOLDER, false)
                     .put(ChromeFeatureList.GRID_TAB_SWITCHER_FOR_TABLETS, false)
                     .put(ChromeFeatureList.TAB_GROUPS_FOR_TABLETS, false)
+                    .put(ChromeFeatureList.TAB_STRIP_IMPROVEMENTS, false)
                     .build();
 
     /**
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 5074b147..38276e7 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -506,6 +506,7 @@
     public static final String TAB_REPARENTING = "TabReparenting";
     public static final String TAB_SWITCHER_ON_RETURN = "TabSwitcherOnReturn";
     public static final String TAB_TO_GTS_ANIMATION = "TabToGTSAnimation";
+    public static final String TAB_STRIP_IMPROVEMENTS = "TabStripImprovements";
     public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
     public static final String TEST_DEFAULT_ENABLED = "TestDefaultEnabled";
     public static final String THEME_REFACTOR_ANDROID = "ThemeRefactorAndroid";
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_interactive_uitest.cc b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_interactive_uitest.cc
similarity index 87%
rename from extensions/browser/guest_view/mime_handler_view/mime_handler_view_interactive_uitest.cc
rename to chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_interactive_uitest.cc
index 494d5453..1a1e483 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_interactive_uitest.cc
+++ b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_interactive_uitest.cc
@@ -2,14 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/base_paths.h"
 #include "base/bind.h"
-#include "base/files/file_util.h"
+#include "base/files/file_path.h"
 #include "base/location.h"
-#include "base/memory/ptr_util.h"
-#include "base/path_service.h"
 #include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -17,16 +13,13 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/guest_view/browser/guest_view_manager_delegate.h"
 #include "components/guest_view/browser/test_guest_view_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_widget_host_view.h"
-#include "content/public/common/content_features.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/api/extensions_api_client.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
 #include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
 #include "extensions/common/constants.h"
@@ -35,20 +28,17 @@
 #include "third_party/blink/public/common/input/web_pointer_properties.h"
 
 using guest_view::GuestViewManager;
-using guest_view::GuestViewManagerDelegate;
 using guest_view::TestGuestViewManager;
-using guest_view::TestGuestViewManagerFactory;
 
 namespace extensions {
 
-// Counts the number of URL requests made for a given URL.
-class MimeHandlerViewTest : public ExtensionApiTest {
+class ChromeMimeHandlerViewInteractiveUITest : public ExtensionApiTest {
  public:
-  MimeHandlerViewTest() {
+  ChromeMimeHandlerViewInteractiveUITest() {
     GuestViewManager::set_factory_for_testing(&factory_);
   }
 
-  ~MimeHandlerViewTest() override {}
+  ~ChromeMimeHandlerViewInteractiveUITest() override = default;
 
   void SetUpOnMainThread() override {
     ExtensionApiTest::SetUpOnMainThread();
@@ -111,8 +101,7 @@
   }
 
  private:
-  TestGuestViewManagerFactory factory_;
-  base::test::ScopedFeatureList scoped_feature_list_;
+  guest_view::TestGuestViewManagerFactory factory_;
 };
 
 // Test is flaky on Linux.  https://crbug.com/877627
@@ -121,7 +110,8 @@
 #else
 #define MAYBE_Fullscreen Fullscreen
 #endif
-IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, MAYBE_Fullscreen) {
+IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewInteractiveUITest,
+                       MAYBE_Fullscreen) {
   RunTest("testFullscreen.csv");
 }
 
@@ -148,7 +138,8 @@
 #else
 #define MAYBE_EscapeExitsFullscreen EscapeExitsFullscreen
 #endif
-IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, MAYBE_EscapeExitsFullscreen) {
+IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewInteractiveUITest,
+                       MAYBE_EscapeExitsFullscreen) {
   // Use the testing subclass of MimeHandlerViewGuest.
   GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
       base::BindRepeating(&TestMimeHandlerViewGuest::Create));
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
index 1e81a6d8..3519bae 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -57,10 +57,13 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/shell.h"
-#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
-#include "ui/base/ui_base_features.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
+#include "ui/base/ui_base_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 #if BUILDFLAG(IS_MAC)
 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
 #endif  // BUILDFLAG(IS_MAC)
@@ -281,6 +284,7 @@
           blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE &&
       kIsLoopbackAudioSupported;
 
+#if BUILDFLAG(IS_CHROMEOS)
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   const content::DesktopMediaID screen_id =
       content::DesktopMediaID::RegisterNativeWindow(
@@ -288,21 +292,26 @@
           primary_root_window_for_testing_
               ? primary_root_window_for_testing_
               : ash::Shell::Get()->GetPrimaryRootWindow());
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  const content::DesktopMediaID screen_id = content::DesktopMediaID(
+      content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   // base::Unretained(this) is safe because DesktopCaptureAccessHandler is owned
   // by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
   // destroyed when the browser process terminates.
-  policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
+  policy::DlpContentManager::Get()->CheckScreenShareRestriction(
       screen_id, GetApplicationTitle(web_contents, extension),
       base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
                      base::Unretained(this), web_contents->GetWeakPtr(),
                      std::move(pending_request), screen_id, capture_audio));
   return;
-#else   // BUILDFLAG(IS_CHROMEOS_ASH)
+#else   // BUILDFLAG(IS_CHROMEOS)
   const content::DesktopMediaID screen_id = content::DesktopMediaID(
       content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
   AcceptRequest(web_contents, std::move(pending_request), screen_id,
                 capture_audio);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 }
 
 bool DesktopCaptureAccessHandler::SupportsStreamType(
@@ -451,21 +460,21 @@
     return;
   }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // base::Unretained(this) is safe because DesktopCaptureAccessHandler is owned
   // by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
   // destroyed when the browser process terminates.
-  policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
+  policy::DlpContentManager::Get()->CheckScreenShareRestriction(
       media_id, pending_request->application_title,
       base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
                      base::Unretained(this), web_contents->GetWeakPtr(),
                      std::move(pending_request), media_id,
                      ShouldCaptureAudio(media_id, request)));
-#else  // BUILDFLAG(IS_CHROMEOS_ASH)
+#else  // BUILDFLAG(IS_CHROMEOS)
   AcceptRequest(web_contents, std::move(pending_request), media_id,
                 ShouldCaptureAudio(media_id, request));
 
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 }
 
 void DesktopCaptureAccessHandler::ProcessChangeSourceRequest(
@@ -608,20 +617,20 @@
              blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
              /*ui=*/nullptr);
   } else {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
     // base::Unretained(this) is safe because DesktopCaptureAccessHandler is
     // owned by MediaCaptureDevicesDispatcher, which is a lazy singleton which
     // is destroyed when the browser process terminates.
-    policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
+    policy::DlpContentManager::Get()->CheckScreenShareRestriction(
         media_id, application_title,
         base::BindOnce(&DesktopCaptureAccessHandler::OnDlpRestrictionChecked,
                        base::Unretained(this), web_contents->GetWeakPtr(),
                        std::move(pending_request), media_id,
                        media_id.audio_share));
-#else   // BUILDFLAG(IS_CHROMEOS_ASH)
+#else   // BUILDFLAG(IS_CHROMEOS)
     AcceptRequest(web_contents, std::move(pending_request), media_id,
                   media_id.audio_share);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
   }
   if (!queue.empty())
     ProcessQueuedAccessRequest(queue, web_contents);
@@ -675,7 +684,7 @@
       .Run(devices, blink::mojom::MediaStreamRequestResult::OK, std::move(ui));
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 void DesktopCaptureAccessHandler::OnDlpRestrictionChecked(
     base::WeakPtr<content::WebContents> web_contents,
     std::unique_ptr<PendingAccessRequest> pending_request,
@@ -701,4 +710,4 @@
   AcceptRequest(web_contents.get(), std::move(pending_request), media_id,
                 capture_audio);
 }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.h b/chrome/browser/media/webrtc/desktop_capture_access_handler.h
index e2554b0..35116048 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.h
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.h
@@ -9,7 +9,6 @@
 #include <string>
 #include <utility>
 
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/media/capture_access_handler_base.h"
 #include "chrome/browser/media/media_access_handler.h"
 #include "chrome/browser/media/webrtc/desktop_media_list.h"
@@ -19,11 +18,11 @@
 #include "content/public/browser/desktop_media_id.h"
 #include "content/public/browser/media_stream_request.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 namespace aura {
 class Window;
 }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace extensions {
 class Extension;
@@ -105,7 +104,7 @@
 
   WebContentsCollection web_contents_collection_;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // Called back after checking Data Leak Prevention (DLP) restrictions.
   void OnDlpRestrictionChecked(
       base::WeakPtr<content::WebContents> web_contents,
@@ -115,7 +114,7 @@
       bool is_dlp_allowed);
 
   aura::Window* primary_root_window_for_testing_ = nullptr;
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 };
 
 #endif  // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
index 7e0dabe..3fe8b3b 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
@@ -12,7 +12,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
@@ -31,10 +30,10 @@
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h"
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h"
 #include "ui/aura/window.h"
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_MAC)
 #include "base/test/scoped_feature_list.h"
@@ -156,11 +155,11 @@
     return access_handler_->pending_requests_;
   }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   void SetPrimaryRootWindow(aura::Window* window) {
     access_handler_->primary_root_window_for_testing_ = window;
   }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
  protected:
   raw_ptr<FakeDesktopMediaPickerFactory> picker_factory_;
@@ -369,12 +368,12 @@
   extensions::ExtensionBuilder extensionBuilder(kComponentExtension);
   extensionBuilder.SetLocation(extensions::mojom::ManifestLocation::kComponent);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   std::unique_ptr<aura::Window> primary_root_window =
       std::make_unique<aura::Window>(/*delegate=*/nullptr);
   primary_root_window->Init(ui::LAYER_NOT_DRAWN);
   SetPrimaryRootWindow(primary_root_window.get());
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   blink::mojom::MediaStreamRequestResult result;
   blink::MediaStreamDevices devices;
@@ -387,11 +386,11 @@
   EXPECT_EQ(1u, devices.size());
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 TEST_F(DesktopCaptureAccessHandlerTest, ScreenCaptureAccessDlpRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([&](const content::DesktopMediaID& media_id,
@@ -424,8 +423,8 @@
 
 TEST_F(DesktopCaptureAccessHandlerTest, ScreenCaptureAccessDlpNotRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([&](const content::DesktopMediaID& media_id,
@@ -459,8 +458,8 @@
 TEST_F(DesktopCaptureAccessHandlerTest,
        ScreenCaptureAccessDlpWebContentsDestroyed) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .Times(1)
@@ -495,8 +494,8 @@
 
 TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamDlpRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .Times(1)
@@ -528,8 +527,8 @@
 
 TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamDlpNotRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .Times(1)
@@ -561,8 +560,8 @@
 
 TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceDlpRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .Times(1)
@@ -585,8 +584,8 @@
 
 TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceDlpNotRestricted) {
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .Times(1)
@@ -606,4 +605,4 @@
   EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
   EXPECT_EQ(1u, devices.size());
 }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
index 843fbb3..f5a5d87 100644
--- a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
+++ b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
@@ -15,7 +15,6 @@
 #include "base/containers/contains.h"
 #include "base/notreached.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
@@ -47,8 +46,8 @@
 #include "extensions/common/extension.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
 #endif
 
 using content::BrowserThread;
@@ -241,8 +240,8 @@
                                 ui_ ? base::OnceClosure() : stop_callback);
     }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    policy::DlpContentManagerAsh::Get()->OnScreenCaptureStarted(
+#if BUILDFLAG(IS_CHROMEOS)
+    policy::DlpContentManager::Get()->OnScreenCaptureStarted(
         label, screen_capture_ids, application_title_, stop_callback,
         state_change_callback);
 #endif
@@ -258,9 +257,8 @@
 
   void OnDeviceStopped(const std::string& label,
                        const content::DesktopMediaID& media_id) override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    policy::DlpContentManagerAsh::Get()->OnScreenCaptureStopped(label,
-                                                                media_id);
+#if BUILDFLAG(IS_CHROMEOS)
+    policy::DlpContentManager::Get()->OnScreenCaptureStopped(label, media_id);
 #endif
   }
 
diff --git a/chrome/browser/media/webrtc/tab_capture_access_handler.cc b/chrome/browser/media/webrtc/tab_capture_access_handler.cc
index 49a2cebd..dab415f 100644
--- a/chrome/browser/media/webrtc/tab_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/tab_capture_access_handler.cc
@@ -8,7 +8,6 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
 #include "chrome/browser/media/webrtc/capture_policy_utils.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
@@ -24,9 +23,9 @@
 #include "extensions/common/permissions/permissions_data.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace {
 // This helper class is designed to live as long as the capture, and is used
@@ -172,7 +171,7 @@
   const bool is_allowlisted_extension =
       IsExtensionAllowedForScreenCapture(extension);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   if (request.video_type ==
       blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE) {
     // Use extension name as title for extensions and host/origin for drive-by
@@ -189,7 +188,7 @@
     // base::Unretained(this) is safe because TabCaptureAccessHandler is owned
     // by MediaCaptureDevicesDispatcher, which is a lazy singleton which is
     // destroyed when the browser process terminates.
-    policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
+    policy::DlpContentManager::Get()->CheckScreenShareRestriction(
         media_id, application_title,
         base::BindOnce(
             &TabCaptureAccessHandler::OnDlpRestrictionChecked,
@@ -201,7 +200,7 @@
             std::move(media_ui)));
     return;
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   AcceptRequest(web_contents, request, std::move(callback),
                 is_allowlisted_extension, std::move(media_ui));
@@ -226,7 +225,7 @@
                           std::move(ui));
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 void TabCaptureAccessHandler::OnDlpRestrictionChecked(
     base::WeakPtr<content::WebContents> web_contents,
     std::unique_ptr<PendingAccessRequest> pending_request,
@@ -250,4 +249,4 @@
              /*ui=*/nullptr);
   }
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/media/webrtc/tab_capture_access_handler.h b/chrome/browser/media/webrtc/tab_capture_access_handler.h
index 1d2d527..5ef33b8 100644
--- a/chrome/browser/media/webrtc/tab_capture_access_handler.h
+++ b/chrome/browser/media/webrtc/tab_capture_access_handler.h
@@ -43,7 +43,7 @@
                      bool is_allowlisted_extension,
                      std::unique_ptr<MediaStreamUI> media_ui);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // Called back after checking Data Leak Prevention (DLP) restrictions.
   void OnDlpRestrictionChecked(
       base::WeakPtr<content::WebContents> web_contents,
@@ -51,7 +51,7 @@
       std::unique_ptr<MediaStreamUI> media_ui,
       bool is_dlp_allowed);
 
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 };
 
 #endif  // CHROME_BROWSER_MEDIA_WEBRTC_TAB_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc b/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc
index de40027..f9406de 100644
--- a/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc
+++ b/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
 #include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
@@ -23,10 +22,10 @@
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
-#include "chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h"
-#endif
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
+#include "chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace {
 constexpr char kOrigin[] = "https://origin/";
@@ -114,7 +113,7 @@
             devices[0].type);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 TEST_F(TabCaptureAccessHandlerTest, DlpRestricted) {
   const content::DesktopMediaID source(
       content::DesktopMediaID::TYPE_WEB_CONTENTS,
@@ -124,8 +123,8 @@
           web_contents()->GetMainFrame()->GetRoutingID()));
 
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([](const content::DesktopMediaID& media_id,
@@ -155,8 +154,8 @@
           web_contents()->GetMainFrame()->GetRoutingID()));
 
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([](const content::DesktopMediaID& media_id,
@@ -186,8 +185,8 @@
           web_contents()->GetMainFrame()->GetRoutingID()));
 
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([&](const content::DesktopMediaID& media_id,
@@ -208,4 +207,4 @@
   EXPECT_EQ(kInvalidResult, result);
   EXPECT_EQ(0u, devices.size());
 }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
index b2c10b9..5604d443 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
@@ -486,6 +486,43 @@
   EXPECT_FALSE(GetContentAnnotationsForURL(url).has_value());
 }
 
+IN_PROC_BROWSER_TEST_F(PageContentAnnotationsServiceNoHistoryTest,
+                       ModelExecutesAndUsesCachedResult) {
+  {
+    base::HistogramTester histogram_tester;
+
+    GURL url(embedded_test_server()->GetURL("a.com", "/hello.html"));
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+    RetryForHistogramUntilCountReached(
+        &histogram_tester,
+        "OptimizationGuide.PageContentAnnotationsService.ContentAnnotated", 1);
+
+    histogram_tester.ExpectUniqueSample(
+        "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+        false, 1);
+  }
+  {
+    base::HistogramTester histogram_tester;
+    GURL url2(embedded_test_server()->GetURL("a.com", "/hello.html"));
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
+
+    RetryForHistogramUntilCountReached(
+        &histogram_tester,
+        "OptimizationGuide.PageContentAnnotationsService.ContentAnnotated", 1);
+
+    histogram_tester.ExpectUniqueSample(
+        "OptimizationGuide.PageContentAnnotationsService.ContentAnnotated",
+        true, 1);
+
+    base::RunLoop().RunUntilIdle();
+
+    histogram_tester.ExpectUniqueSample(
+        "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+        true, 1);
+  }
+}
+
 class PageContentAnnotationsServiceBatchVisitTest
     : public PageContentAnnotationsServiceNoHistoryTest {
  public:
@@ -530,6 +567,11 @@
 
   base::RunLoop().RunUntilIdle();
 
+  // The cache is missed because we are batching requests. The cache check
+  // happens before adding a visit annotation request to the batch.
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+      false, 2);
   histogram_tester.ExpectUniqueSample(
       "PageContentAnnotations.AnnotateVisit.BatchAnnotationStarted", true, 1);
 
@@ -598,6 +640,10 @@
   histogram_tester.ExpectUniqueSample(
       "PageContentAnnotations.AnnotateVisit.QueueFullVisitDropped", true, 1);
 
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+      false, 4);
+
   histogram_tester.ExpectTotalCount(
       "OptimizationGuide.PageContentAnnotationsService."
       "ContentAnnotationsStorageStatus",
diff --git a/chrome/browser/renderer_host/chrome_extension_message_filter.cc b/chrome/browser/renderer_host/chrome_extension_message_filter.cc
index a0c4160..a0e748d 100644
--- a/chrome/browser/renderer_host/chrome_extension_message_filter.cc
+++ b/chrome/browser/renderer_host/chrome_extension_message_filter.cc
@@ -26,9 +26,9 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/browser/l10n_file_util.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_set.h"
-#include "extensions/common/file_util.h"
 #include "extensions/common/manifest_handlers/default_locale_handler.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "extensions/common/message_bundle.h"
@@ -129,8 +129,8 @@
   if (default_locale.empty()) {
     // A little optimization: send the answer here to avoid an extra thread hop.
     std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
-        extensions::file_util::LoadNonLocalizedMessageBundleSubstitutionMap(
-            extension_id));
+        extensions::l10n_file_util::
+            LoadNonLocalizedMessageBundleSubstitutionMap(extension_id));
     ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
                                                         *dictionary_map);
     Send(reply_msg);
@@ -173,7 +173,7 @@
     extension_l10n_util::GzippedMessagesPermission gzip_permission,
     IPC::Message* reply_msg) {
   std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
-      extensions::file_util::LoadMessageBundleSubstitutionMapFromPaths(
+      extensions::l10n_file_util::LoadMessageBundleSubstitutionMapFromPaths(
           extension_paths, main_extension_id, default_locale, gzip_permission));
 
   ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index fc2045d..5d73e76 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -144,7 +144,6 @@
         "new_tab_page_instant:closure_compile",
         "ntp4:closure_compile",
         "omnibox:closure_compile",
-        "quota_internals:closure_compile",
         "settings:closure_compile",
         "sync_file_system_internals:closure_compile",
         "usb_internals:closure_compile",
@@ -316,7 +315,6 @@
     "$root_gen_dir/chrome/invalidations_resources.pak",
     "$root_gen_dir/chrome/net_internals_resources.pak",
     "$root_gen_dir/chrome/omnibox_resources.pak",
-    "$root_gen_dir/chrome/quota_internals_resources.pak",
     "$root_gen_dir/chrome/usb_internals_resources.pak",
     "$root_gen_dir/chrome/webrtc_logs_resources.pak",
     "$root_gen_dir/components/dev_ui_components_resources.pak",
@@ -335,7 +333,6 @@
     "//chrome/browser/resources/media:webrtc_logs_resources",
     "//chrome/browser/resources/net_internals:resources",
     "//chrome/browser/resources/omnibox:resources",
-    "//chrome/browser/resources/quota_internals:resources",
     "//chrome/browser/resources/usb_internals:resources",
     "//components/download/resources/download_internals:resources",
     "//components/resources:dev_ui_components_resources",
diff --git a/chrome/browser/resources/chromeos/arc_support/OWNERS b/chrome/browser/resources/chromeos/arc_support/OWNERS
index e844cda..73a4704 100644
--- a/chrome/browser/resources/chromeos/arc_support/OWNERS
+++ b/chrome/browser/resources/chromeos/arc_support/OWNERS
@@ -1,2 +1,6 @@
-lgcheng@chromium.org
+lgcheng@google.com
 mhasank@chromium.org
+
+per-file recommend_app_list_view.*=rrsilva@google.com
+per-file recommend_app_list_view.*=rsorokin@chromium.org
+per-file recommend_app_list_view.*=tellier@google.com
\ No newline at end of file
diff --git a/chrome/browser/resources/gaia_auth_host/authenticator.js b/chrome/browser/resources/gaia_auth_host/authenticator.js
index 32f32a7..680f87e 100644
--- a/chrome/browser/resources/gaia_auth_host/authenticator.js
+++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -342,6 +342,11 @@
       this.syncTrustedVaultKeys_ = msg.value;
     },
     'closeView'(msg) {
+      // We need to resend the message to make sure it comes after the API
+      // 'confirm' call. The API calls go via different channel.
+      window.postMessage({method: 'resendCloseView'}, window.origin);
+    },
+    'resendCloseView'(msg) {
       if (!this.enableCloseView_) {
         return;
       }
@@ -949,7 +954,7 @@
      * Returns true if given HTML5 message is received from the webview element.
      * @param {Object} e Payload of the received HTML5 message.
      */
-    isGaiaMessage(e) {
+    isGaiaMessage_(e) {
       if (!this.isWebviewEvent_(e)) {
         return false;
       }
@@ -968,12 +973,28 @@
     }
 
     /**
+     * Returns true if given HTML5 message is received from the current window.
+     * @param {Object} e Payload of the received HTML5 message.
+     */
+    isSelfMessage_(e) {
+      if (e.origin !== window.origin) {
+        return false;
+      }
+
+      if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
+        return false;
+      }
+
+      return true;
+    }
+
+    /**
      * Invoked when an HTML5 message is received from the webview element.
      * @param {Object} e Payload of the received HTML5 message.
      * @private
      */
     onMessageFromWebview_(e) {
-      if (!this.isGaiaMessage(e)) {
+      if (!this.isGaiaMessage_(e) && !this.isSelfMessage_(e)) {
         return;
       }
 
diff --git a/chrome/browser/resources/inline_login/arc_account_picker_app.html b/chrome/browser/resources/inline_login/arc_account_picker_app.html
index b6920f8..0a347e885 100644
--- a/chrome/browser/resources/inline_login/arc_account_picker_app.html
+++ b/chrome/browser/resources/inline_login/arc_account_picker_app.html
@@ -83,7 +83,7 @@
   <p class="secondary">$i18nRaw{accountManagerDialogArcAccountPickerBody}</p>
 
   <template is="dom-repeat" id="accountsContainer" items="[[accounts]]">
-    <div class="account-item">
+    <div class="account-item" on-click="makeAvailableInArc_">
       <div class="account-icon"
         style="background-image: [[getIconImageSet_(item.image)]]">
       </div>
diff --git a/chrome/browser/resources/inline_login/arc_account_picker_app.js b/chrome/browser/resources/inline_login/arc_account_picker_app.js
index a3f94b1..91eb33e6 100644
--- a/chrome/browser/resources/inline_login/arc_account_picker_app.js
+++ b/chrome/browser/resources/inline_login/arc_account_picker_app.js
@@ -7,6 +7,7 @@
 import {getImage} from '//resources/js/icon.js';
 import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {Account} from './inline_login_browser_proxy.js';
+import {InlineLoginBrowserProxyImpl} from './inline_login_browser_proxy.js';
 
 /** @polymer */
 class ArcAccountPickerAppElement extends PolymerElement {
@@ -56,6 +57,15 @@
   addAccount_() {
     this.dispatchEvent(new CustomEvent('add-account'));
   }
+
+  /**
+   * @param {!{model: !{item: Account}, target: !Element}} event
+   * @private
+   */
+  makeAvailableInArc_(event) {
+    InlineLoginBrowserProxyImpl.getInstance().makeAvailableInArc(
+        event.model.item);
+  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/inline_login/inline_login_browser_proxy.js b/chrome/browser/resources/inline_login/inline_login_browser_proxy.js
index 8300b91..0420c55d 100644
--- a/chrome/browser/resources/inline_login/inline_login_browser_proxy.js
+++ b/chrome/browser/resources/inline_login/inline_login_browser_proxy.js
@@ -76,6 +76,11 @@
   skipWelcomePage(skip) {}
 
   /**
+   * @param {Account} account
+   */
+  makeAvailableInArc(account) {}
+
+  /**
    * Send 'getAccountsNotAvailableInArc' message to the handler. The promise
    * will be resolved with the list of accounts that are not available in ARC.
    * @return {Promise<Array<Account>>}
@@ -148,6 +153,11 @@
   }
 
   /** @override */
+  makeAvailableInArc(account) {
+    chrome.send('makeAvailableInArc', [account]);
+  }
+
+  /** @override */
   getDialogArguments() {
     return chrome.getVariableValue('dialogArguments');
   }
diff --git a/chrome/browser/resources/print_preview/BUILD.gn b/chrome/browser/resources/print_preview/BUILD.gn
index 5a16fe7..15208628 100644
--- a/chrome/browser/resources/print_preview/BUILD.gn
+++ b/chrome/browser/resources/print_preview/BUILD.gn
@@ -40,8 +40,13 @@
 
 generate_grd("build_grd") {
   input_files = [ "print_preview.html" ]
-  input_files_base_dir = rebase_path(".", "//")
-  deps = [ "../pdf:build_print_preview_grdp" ]
+  input_files_base_dir =
+      rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
+  deps = [
+    ":preprocess_html",
+    "../pdf:build_print_preview_grdp",
+  ]
+
   grdp_files = [ "$root_gen_dir/chrome/browser/resources/pdf/print_preview_pdf_resources.grdp" ]
   if (optimize_webui) {
     deps += [ ":build" ]
@@ -59,6 +64,12 @@
   js_files = web_component_files
 }
 
+preprocess_if_expr("preprocess_html") {
+  in_folder = "./"
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  in_files = [ "print_preview.html" ]
+}
+
 preprocess_if_expr("preprocess") {
   in_folder = "./"
   out_folder = "$target_gen_dir/$preprocess_folder"
diff --git a/chrome/browser/resources/print_preview/print_preview.html b/chrome/browser/resources/print_preview/print_preview.html
index da49aa1..c4620d22a 100644
--- a/chrome/browser/resources/print_preview/print_preview.html
+++ b/chrome/browser/resources/print_preview/print_preview.html
@@ -55,6 +55,10 @@
   <print-preview-app></print-preview-app>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
+<if expr="chromeos_ash">
+  <link rel="stylesheet"
+      href="chrome://resources/chromeos/colors/cros_styles.css">
+</if>
   <script type="module" src="print_preview.js"></script>
 </body>
 </html>
diff --git a/chrome/browser/resources/quota_internals/BUILD.gn b/chrome/browser/resources/quota_internals/BUILD.gn
deleted file mode 100644
index 3211b6a4..0000000
--- a/chrome/browser/resources/quota_internals/BUILD.gn
+++ /dev/null
@@ -1,58 +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.
-
-import("//chrome/common/features.gni")
-import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/grit/grit_rule.gni")
-import("//ui/webui/resources/tools/generate_grd.gni")
-
-generate_grd("build_grd") {
-  grd_prefix = "quota_internals"
-  out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
-  input_files = [
-    "event_handler.js",
-    "main.css",
-    "main.html",
-    "message_dispatcher.js",
-  ]
-  input_files_base_dir = rebase_path(".", "//")
-}
-
-js_type_check("closure_compile") {
-  deps = [
-    ":event_handler",
-    ":message_dispatcher",
-  ]
-}
-
-js_library("event_handler") {
-  deps = [
-    ":message_dispatcher",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js:util.m",
-    "//ui/webui/resources/js/cr:ui.m",
-    "//ui/webui/resources/js/cr/ui:tabs",
-    "//ui/webui/resources/js/cr/ui:tree",
-  ]
-}
-
-js_library("message_dispatcher") {
-}
-
-grit("resources") {
-  defines = chrome_grit_defines
-
-  # These arguments are needed since the grd is generated at build time.
-  enable_input_discovery_for_gn_analyze = false
-  source = "$target_gen_dir/quota_internals_resources.grd"
-  deps = [ ":build_grd" ]
-
-  outputs = [
-    "grit/quota_internals_resources.h",
-    "grit/quota_internals_resources_map.cc",
-    "grit/quota_internals_resources_map.h",
-    "quota_internals_resources.pak",
-  ]
-  output_dir = "$root_gen_dir/chrome"
-}
diff --git a/chrome/browser/resources/quota_internals/DIR_METADATA b/chrome/browser/resources/quota_internals/DIR_METADATA
deleted file mode 100644
index d669a4a..0000000
--- a/chrome/browser/resources/quota_internals/DIR_METADATA
+++ /dev/null
@@ -1,4 +0,0 @@
-monorail: {
-  component: "Blink>Storage>Quota"
-}
-team_email: "storage-dev@chromium.org"
diff --git a/chrome/browser/resources/quota_internals/OWNERS b/chrome/browser/resources/quota_internals/OWNERS
deleted file mode 100644
index a61a714..0000000
--- a/chrome/browser/resources/quota_internals/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://storage/browser/quota/OWNERS
diff --git a/chrome/browser/resources/quota_internals/event_handler.js b/chrome/browser/resources/quota_internals/event_handler.js
deleted file mode 100644
index c267293..0000000
--- a/chrome/browser/resources/quota_internals/event_handler.js
+++ /dev/null
@@ -1,495 +0,0 @@
-// Copyright (c) 2011 The Chromium 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 {addWebUIListener} from 'chrome://resources/js/cr.m.js';
-import {decorate} from 'chrome://resources/js/cr/ui.m.js';
-import {TabBox} from 'chrome://resources/js/cr/ui/tabs.js';
-import {Tree, TreeItem} from 'chrome://resources/js/cr/ui/tree.js';
-import {$} from 'chrome://resources/js/util.m.js';
-
-import {requestInfo, triggerStoragePressure} from './message_dispatcher.js';
-
-/**
- * @param {Object} object Object to be checked.
- * @return {boolean} true if |object| is {}.
- * @private
- */
-function isEmptyObject_(object) {
-  for (const i in object) {
-    return false;
-  }
-  return true;
-}
-
-/**
- * Copy properties from |source| to |destination|.
- * @param {Object} source Source of the copy.
- * @param {Object} destination Destination of the copy.
- * @return {Object} |destination|.
- * @private
- */
-function copyAttributes_(source, destination) {
-  for (const i in source) {
-    destination[i] = source[i];
-  }
-  return destination;
-}
-
-/**
- * Returns 'N/A' (Not Available) text if |value| is undefined.
- * @param {*} value Object to print.
- * @return {string} 'N/A' or ''.
- * @private
- */
-function checkIfAvailable_(value) {
-  return value === undefined ? 'N/A' : '';
-}
-
-/**
- * Returns |value| itself if |value| is not undefined,
- * else returns 'N/A' text.
- * @param {?string} value String to print.
- * @return {string} 'N/A' or |value|.
- * @private
- */
-function stringToText_(value) {
-  return checkIfAvailable_(value) || /** @type {string} */ (value);
-}
-
-/**
- * Separates |value| into segments.
- * The length of first segment is at most |maxLength|.
- * Length of other following segments are just |maxLength|.
- * e.g. separateBackward_('abcdefghijk', 4) == ['abc','defg','hijk'];
- * @param {string} value String to be separated.
- * @param {number} maxLength Max length of segments.
- * @return {Array<string>} Array of segments.
- * @private
- */
-function separateBackward_(value, maxLength) {
-  const result = [];
-  while (value.length > maxLength) {
-    result.unshift(value.slice(-3));
-    value = value.slice(0, -3);
-  }
-  result.unshift(value);
-  return result;
-}
-
-/**
- * Returns formatted string from number as number of bytes.
- * e.g. numBytesToText(123456789) = '123.45 MB (123,456,789 B)'.
- * If |value| is undefined, this function returns 'N/A'.
- * @param {?number} value Number to print.
- * @return {string} 'N/A' or formatted |value|.
- * @private
- */
-function numBytesToText_(value) {
-  let result = checkIfAvailable_(value);
-  if (result) {
-    return result;
-  }
-
-  const segments = separateBackward_(value.toString(), 3);
-  result = segments.join(',') + ' B';
-
-  if (segments.length > 1) {
-    const UNIT = [' B', ' KB', ' MB', ' GB', ' TB', ' PB'];
-    result = segments[0] + '.' + segments[1].slice(0, 2) +
-        UNIT[Math.min(segments.length, UNIT.length) - 1] + ' (' + result + ')';
-  }
-
-  return result;
-}
-
-/**
- * Return formatted date |value| if |value| is not undefined.
- * If |value| is undefined, this function returns 'N/A'.
- * @param {?number} value Number of milliseconds since
- *   UNIX epoch time (0:00, Jan 1, 1970, UTC).
- * @return {string} Formatted text of date or 'N/A'.
- * @private
- */
-function dateToText(value) {
-  let result = checkIfAvailable_(value);
-  if (result) {
-    return result;
-  }
-
-  const time = new Date(value);
-  const now = new Date();
-  const delta = Date.now() - value;
-
-  const SECOND = 1000;
-  const MINUTE = 60 * SECOND;
-  const HOUR = 60 * MINUTE;
-  const DAY = 23 * HOUR;
-  const WEEK = 7 * DAY;
-
-  const SHOW_SECOND = 5 * MINUTE;
-  const SHOW_MINUTE = 5 * HOUR;
-  const SHOW_HOUR = 3 * DAY;
-  const SHOW_DAY = 2 * WEEK;
-  const SHOW_WEEK = 3 * 30 * DAY;
-
-  if (delta < 0) {
-    result = 'access from future ';
-  } else if (delta < SHOW_SECOND) {
-    result = Math.ceil(delta / SECOND) + ' sec ago ';
-  } else if (delta < SHOW_MINUTE) {
-    result = Math.ceil(delta / MINUTE) + ' min ago ';
-  } else if (delta < SHOW_HOUR) {
-    result = Math.ceil(delta / HOUR) + ' hr ago ';
-  } else if (delta < SHOW_WEEK) {
-    result = Math.ceil(delta / DAY) + ' day ago ';
-  }
-
-  result += '(' + time.toString() + ')';
-  return result;
-}
-
-/**
- * Available disk space.
- * @type {number|undefined}
- */
-let availableSpace = undefined;
-
-/**
- * Root of the quota data tree,
- * holding userdata as |treeViewObject.detail|.
- * @type {Tree}
- */
-let treeViewObject;
-
-/**
- * Key-value styled statistics data.
- * This WebUI does not touch contents, just show.
- * The value is hold as |statistics[key].detail|.
- * @type {Object<string,Element>}
- */
-const statistics = {};
-
-/**
- * Initialize and return |treeViewObject|.
- * @return {!Tree} Initialized |treeViewObject|.
- */
-function getTreeViewObject() {
-  if (!treeViewObject) {
-    treeViewObject = /** @type {!Tree} */ ($('tree-view'));
-    decorate(treeViewObject, Tree);
-    treeViewObject.detail = {payload: {}, children: {}};
-    treeViewObject.addEventListener('change', updateDescription);
-  }
-  return treeViewObject;
-}
-
-/**
- * Initialize and return a tree item, that represents specified storage type.
- * @param {!string} type Storage type.
- * @return {TreeItem} Initialized |storageObject|.
- */
-function getStorageObject(type) {
-  const treeViewObject = getTreeViewObject();
-  let storageObject = treeViewObject.detail.children[type];
-  if (!storageObject) {
-    storageObject =
-        new TreeItem({label: type, detail: {payload: {}, children: {}}});
-    storageObject.mayHaveChildren_ = true;
-    treeViewObject.detail.children[type] = storageObject;
-    treeViewObject.add(storageObject);
-  }
-  return storageObject;
-}
-
-/**
- * Initialize and return a tree item, that represents specified
- *  storage type and hostname.
- * @param {!string} type Storage type.
- * @param {!string} host Hostname.
- * @return {TreeItem} Initialized |hostObject|.
- */
-function getHostObject(type, host) {
-  const storageObject = getStorageObject(type);
-  let hostObject = storageObject.detail.children[host];
-  if (!hostObject) {
-    hostObject =
-        new TreeItem({label: host, detail: {payload: {}, children: {}}});
-    hostObject.mayHaveChildren_ = true;
-    storageObject.detail.children[host] = hostObject;
-    storageObject.add(hostObject);
-  }
-  return hostObject;
-}
-
-/**
- * Initialize and return a tree item, that represents specified
- * storage type, hostname and origin url.
- * @param {!string} type Storage type.
- * @param {!string} host Hostname.
- * @param {!string} origin Origin URL.
- * @return {TreeItem} Initialized |originObject|.
- */
-function getOriginObject(type, host, origin) {
-  const hostObject = getHostObject(type, host);
-  let originObject = hostObject.detail.children[origin];
-  if (!originObject) {
-    originObject =
-        new TreeItem({label: origin, detail: {payload: {}, children: {}}});
-    originObject.mayHaveChildren_ = false;
-    hostObject.detail.children[origin] = originObject;
-    hostObject.add(originObject);
-  }
-  return originObject;
-}
-
-/** @param {number} space Total available disk space. */
-function handleAvailableSpace(space) {
-  availableSpace = space;
-  $('diskspace-entry').textContent = numBytesToText_(availableSpace);
-}
-
-/**
- * |data| contains a record which has:
- *   |type|:
- *     Storage type, that is either 'temporary' or 'persistent'.
- *   |usage|:
- *     Total storage usage of all hosts.
- *   |unlimitedUsage|:
- *     Total storage usage of unlimited-quota origins.
- *   |quota|:
- *     Total quota of the storage.
- *
- *  |usage|, |unlimitedUsage| and |quota| can be missing,
- *  and some additional fields can be included.
- * @param {!{
- *     type: string,
- *     usage: ?number,
- *     unlimitedUsage: ?number,
- *     quota: ?string
- * }} data
- */
-function handleGlobalInfo(data) {
-  const storageObject = getStorageObject(data.type);
-  copyAttributes_(data, storageObject.detail.payload);
-  storageObject.reveal();
-  if (getTreeViewObject().selectedItem == storageObject) {
-    updateDescription();
-  }
-}
-
-/**
- * |dataArray| contains records which have:
- *   |host|:
- *     Hostname of the entry. (e.g. 'example.com')
- *   |type|:
- *     Storage type. 'temporary' or 'persistent'
- *   |usage|:
- *     Total storage usage of the host.
- *   |quota|:
- *     Per-host quota.
- *
- * |usage| and |quota| can be missing,
- * and some additional fields can be included.
- * @param {!Array<{
- *     host: string,
- *     type: string,
- *     usage: ?number,
- *     quota: ?number
- * }>} dataArray
- */
-function handlePerHostInfo(dataArray) {
-  for (let i = 0; i < dataArray.length; ++i) {
-    const data = dataArray[i];
-    const hostObject = getHostObject(data.type, data.host);
-    copyAttributes_(data, hostObject.detail.payload);
-    hostObject.reveal();
-    if (getTreeViewObject().selectedItem == hostObject) {
-      updateDescription();
-    }
-  }
-}
-
-/**
- * |dataArray| contains records which have:
- *   |origin|:
- *     Origin URL of the entry.
- *   |type|:
- *     Storage type of the entry. 'temporary' or 'persistent'.
- *   |host|:
- *     Hostname of the entry.
- *   |usedCount|:
- *     Used count of the storage from the origin.
- *   |lastAccessTime|:
- *     Last storage access time from the origin.
- *     Number of milliseconds since UNIX epoch (Jan 1, 1970, 0:00:00 UTC).
- *   |lastModifiedTime|:
- *     Last modified time of the storage from the origin.
- *     Number of milliseconds since UNIX epoch.
- *
- * |usedCount|, |lastAccessTime| and |lastModifiedTime| can be missing,
- * and some additional fields can be included.
- * @param {!Array<!{
- *     origin: string,
- *     type: string,
- *     host: string,
- *     usedCount: ?number,
- *     lastAccessTime: ?number,
- *     lastModifiedTime: ?number
- * }>} dataArray
- */
-function handlePerOriginInfo(dataArray) {
-  for (let i = 0; i < dataArray.length; ++i) {
-    const data = dataArray[i];
-    const originObject = getOriginObject(data.type, data.host, data.origin);
-    copyAttributes_(data, originObject.detail.payload);
-    originObject.reveal();
-    if (getTreeViewObject().selectedItem == originObject) {
-      updateDescription();
-    }
-  }
-}
-
-/**
- * |data| contains misc statistics data as dictionary.
- * @param {!Object} data
- */
-function handleStatistics(data) {
-  for (const key in data) {
-    let entry = statistics[key];
-    if (!entry) {
-      const template = document.querySelector('#table-row-template');
-      entry = template.content.cloneNode(true).querySelector('tr');
-      $('stat-entries').appendChild(entry);
-      statistics[key] = entry;
-    }
-    entry.detail = data[key];
-
-    entry.querySelectorAll('td')[0].textContent = stringToText_(key);
-    entry.querySelectorAll('td')[1].textContent = stringToText_(entry.detail);
-  }
-}
-
-/**
- * @param {!{isStoragePressureEnabled: boolean}} data Contains a boolean
- *     representing whether or not to show the storage pressure UI.
- */
-function handleStoragePressureFlagInfo(data) {
-  $('storage-pressure-loading').hidden = true;
-  if (data.isStoragePressureEnabled) {
-    $('storage-pressure-outer').hidden = false;
-  } else {
-    $('storage-pressure-disabled').hidden = false;
-  }
-}
-
-/**
- * Update description on 'tree-item-description' field with
- * selected item in tree view.
- */
-function updateDescription() {
-  const item = getTreeViewObject().selectedItem;
-  const tbody = $('tree-item-description');
-  tbody.innerHTML = trustedTypes.emptyHTML;
-
-  if (item) {
-    const keyAndLabel = [
-      ['type', 'Storage Type'], ['host', 'Host Name'], ['origin', 'Origin URL'],
-      ['usage', 'Total Storage Usage', numBytesToText_],
-      ['unlimitedUsage', 'Usage of Unlimited Origins', numBytesToText_],
-      ['quota', 'Quota', numBytesToText_], ['usedCount', 'Used count'],
-      ['lastAccessTime', 'Last Access Time', dateToText],
-      ['lastModifiedTime', 'Last Modified Time', dateToText]
-    ];
-    for (let i = 0; i < keyAndLabel.length; ++i) {
-      const key = keyAndLabel[i][0];
-      const label = keyAndLabel[i][1];
-      const entry = item.detail.payload[key];
-      if (entry === undefined) {
-        continue;
-      }
-
-      const normalize = keyAndLabel[i][2] || stringToText_;
-
-      const template = document.querySelector('#table-row-template');
-      const row = template.content.cloneNode(true).querySelector('tr');
-      row.querySelectorAll('td')[0].textContent = label;
-      row.querySelectorAll('td')[1].textContent = normalize(entry);
-      tbody.appendChild(row);
-    }
-  }
-}
-
-/**
- * Dump |treeViewObject| or subtree to a object.
- * @param {(Tree|TreeItem)=} opt_treeitem
- * @return {Object} Dump result object from |treeViewObject|.
- */
-function dumpTreeToObj(opt_treeitem) {
-  const treeitem = opt_treeitem || getTreeViewObject();
-  const res = {};
-  res.payload = treeitem.detail.payload;
-  res.children = [];
-  for (const i in treeitem.detail.children) {
-    const child = treeitem.detail.children[i];
-    res.children.push(dumpTreeToObj(child));
-  }
-
-  if (isEmptyObject_(res.payload)) {
-    delete res.payload;
-  }
-
-  if (res.children.length == 0) {
-    delete res.children;
-  }
-  return res;
-}
-
-/**
- * Dump |statistics| to a object.
- * @return {Object} Dump result object from |statistics|.
- */
-function dumpStatisticsToObj() {
-  const result = {};
-  for (const key in statistics) {
-    result[key] = statistics[key].detail;
-  }
-  return result;
-}
-
-/**
- * Event handler for 'dump-button' 'click'ed.
- * Dump and show all data from WebUI page to 'dump-field' element.
- */
-function dump() {
-  const separator = '========\n';
-
-  $('dump-field').textContent = separator + 'Summary\n' + separator +
-      JSON.stringify({availableSpace: availableSpace}, null, 2) + '\n' +
-      separator + 'Usage And Quota\n' + separator +
-      JSON.stringify(dumpTreeToObj(), null, 2) + '\n' + separator +
-      'Misc Statistics\n' + separator +
-      JSON.stringify(dumpStatisticsToObj(), null, 2);
-}
-
-function onLoad() {
-  decorate('tabbox', TabBox);
-
-  addWebUIListener('AvailableSpaceUpdated', handleAvailableSpace);
-  addWebUIListener('GlobalInfoUpdated', handleGlobalInfo);
-  addWebUIListener('PerHostInfoUpdated', handlePerHostInfo);
-  addWebUIListener('PerOriginInfoUpdated', handlePerOriginInfo);
-  addWebUIListener('StatisticsUpdated', handleStatistics);
-  addWebUIListener('StoragePressureFlagUpdated', handleStoragePressureFlagInfo);
-
-  requestInfo();
-
-  $('refresh-button').addEventListener('click', requestInfo, false);
-  $('dump-button').addEventListener('click', dump, false);
-  $('trigger-notification').addEventListener('click', () => {
-    const origin = $('storage-pressure-origin').value;
-    triggerStoragePressure(origin);
-  }, false);
-}
-
-document.addEventListener('DOMContentLoaded', onLoad, false);
diff --git a/chrome/browser/resources/quota_internals/main.css b/chrome/browser/resources/quota_internals/main.css
deleted file mode 100644
index 3afbeb6e..0000000
--- a/chrome/browser/resources/quota_internals/main.css
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-th,
-td {
-  padding-inline-end: 0.5em;
-  padding-inline-start: 0.5em;
-  text-align: center;
-}
-
-#tree-view-container {
-  float: left;
-  min-width: 15em;
-  width: 20%;
-}
-
-tr:nth-child(odd) {
-  background: rgb(238, 238, 255);
-}
-
-.tree-item:not([may-have-children]) > .tree-row > .tree-label-icon {
-  background-image: url(chrome://resources/images/icon_file.png);
-}
-
-.pressure {
-  margin: 10px 0;
-}
-#storage-pressure-origin {
-  min-width: 20em;
-}
diff --git a/chrome/browser/resources/quota_internals/main.html b/chrome/browser/resources/quota_internals/main.html
deleted file mode 100644
index 87a17f4..0000000
--- a/chrome/browser/resources/quota_internals/main.html
+++ /dev/null
@@ -1,95 +0,0 @@
-<!doctype html>
-<!--
-Copyright (c) 2011 The Chromium Authors. All rights reserved.
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<html dir="ltr" lang="en">
-<title>Quota Internals</title>
-<meta charset="utf-8">
-<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-<link rel="stylesheet" href="main.css">
-
-<link rel="stylesheet" href="chrome://resources/css/tabs.css">
-<link rel="stylesheet" href="chrome://resources/css/tree.css">
-
-<script type="module" src="event_handler.js"></script>
-
-<body>
-
-<tabbox>
-  <tabs>
-    <tab>Summary</tab>
-    <tab>Usage &amp; Quota</tab>
-    <tab>Raw Data Dump</tab>
-  </tabs>
-  <tabpanels>
-    <tabpanel>
-      <!-- Summary -->
-      <h2>Summary</h2>
-
-      <template id="table-row-template">
-        <tr>
-          <td></td>
-          <td></td>
-        </tr>
-      </template>
-
-      <table>
-        <tbody>
-          <tr>
-            <td>Free disk space for the profile directory.</td>
-            <td id="diskspace-entry">N/A</td>
-          </tr>
-        </tbody>
-      </table>
-      <h2>Misc Statistics</h2>
-      <table>
-        <tbody id="stat-entries" class="entries"></tbody>
-      </table>
-      <div id="storage-pressure-section">
-        <h2>Test Storage Pressure Behavior</h2>
-        <div id="storage-pressure-loading">
-          Loading...
-        </div>
-        <div id="storage-pressure-outer" hidden>
-          <div class="pressure">
-            Origin to test:
-            <input id="storage-pressure-origin"
-                   value="https://www.example.com">
-          </div>
-          <div class="pressure">
-            <button id="trigger-notification">
-              Trigger Storage Pressure Notification
-            </button>
-          </div>
-        </div>
-        <div id="storage-pressure-disabled" hidden>
-          Storage Pressure feature disabled.
-        </div>
-      </div>
-    </tabpanel>
-
-    <tabpanel>
-      <!-- Usage and Quota -->
-      <h2>Usage and Quota Database Browser</h2>
-      <div id="tree-view-container">
-        <button id="refresh-button">Refresh</button>
-        <tree id="tree-view"></tree>
-      </div>
-      <table>
-        <thead></thead>
-        <tbody id="tree-item-description"></tbody>
-      </table>
-    </tabpanel>
-
-    <tabpanel>
-      <!-- Raw Data Dump -->
-      <button id="dump-button">Dump Raw Data</button>
-      <pre id="dump-field"></pre>
-    </tabpanel>
-  </tabpanels>
-</tabbox>
-
-</body>
-</html>
diff --git a/chrome/browser/resources/quota_internals/message_dispatcher.js b/chrome/browser/resources/quota_internals/message_dispatcher.js
deleted file mode 100644
index dd452be..0000000
--- a/chrome/browser/resources/quota_internals/message_dispatcher.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Bridge between the browser and the page.
- * In this file:
- *   * define interface to request data from the browser.
- */
-
-/** Post requestInfo message to Browser. */
-export function requestInfo() {
-  chrome.send('requestInfo');
-}
-
-/**
- * Post triggerStoragePressure message to Browser.
- * @param {string} origin
- */
-export function triggerStoragePressure(origin) {
-  chrome.send('triggerStoragePressure', [origin]);
-}
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index 84878253..4273478f 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -316,6 +316,8 @@
                         FeatureConstants.IPH_SHARE_SCREENSHOT_FEATURE);
         return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
                 ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(
+                        DetailedContentType.LIGHTWEIGHT_REACTION, DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.screenshot, R.string.sharing_screenshot)
                 .setFeatureNameForMetrics("SharingHubAndroid.ScreenshotSelected")
                 .setDisableForMultiWindow(true)
@@ -335,6 +337,8 @@
     private FirstPartyOption createLongScreenshotsFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
                 ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(
+                        DetailedContentType.LIGHTWEIGHT_REACTION, DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.long_screenshot, R.string.sharing_long_screenshot)
                 .setFeatureNameForMetrics("SharingHubAndroid.LongScreenshotSelected")
                 .setDisableForMultiWindow(true)
@@ -411,6 +415,8 @@
     private FirstPartyOption createSendTabToSelfFirstPartyOption() {
         return new FirstPartyOptionBuilder(
                 ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(
+                        DetailedContentType.LIGHTWEIGHT_REACTION, DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.send_tab, R.string.send_tab_to_self_share_activity_title)
                 .setFeatureNameForMetrics("SharingHubAndroid.SendTabToSelfSelected")
                 .setOnClickCallback((view) -> {
@@ -430,6 +436,8 @@
     private FirstPartyOption createQrCodeFirstPartyOption() {
         return new FirstPartyOptionBuilder(
                 ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(
+                        DetailedContentType.LIGHTWEIGHT_REACTION, DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.qr_code, R.string.qr_code_share_icon_label)
                 .setFeatureNameForMetrics("SharingHubAndroid.QRCodeSelected")
                 .setOnClickCallback((view) -> {
@@ -472,6 +480,8 @@
     private FirstPartyOption createLightweightReactionsFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
                 ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(
+                        DetailedContentType.LIGHTWEIGHT_REACTION, DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.lightweight_reactions_icon,
                         R.string.sharing_lightweight_reactions)
                 .setFeatureNameForMetrics("SharingHubAndroid.LightweightReactions")
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
index b151179f..c480ba8 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
@@ -354,20 +354,37 @@
 
     @Test
     @MediumTest
+    public void getPropertyModels_webnotes_filtersByDetailedContentType() {
+        setUpChromeProvidedSharingOptionsProviderTest(/*isIncognito=*/false,
+                /*printingEnabled=*/true, LinkGeneration.MAX);
+        List<PropertyModel> propertyModels =
+                mChromeProvidedSharingOptionsProvider.getPropertyModels(
+                        ImmutableSet.of(ContentType.IMAGE), DetailedContentType.WEB_NOTES,
+                        /*isMultiWindow=*/false);
+
+        List<String> expectedModels =
+                ImmutableList.<String>builder()
+                        .add(mActivity.getResources().getString(R.string.sharing_copy_image))
+                        .add(mActivity.getResources().getString(R.string.sharing_save_image))
+                        .build();
+
+        assertCorrectModelsAreInTheRightOrder(propertyModels, expectedModels);
+    }
+
+    @Test
+    @MediumTest
     @Features.EnableFeatures({ChromeFeatureList.LIGHTWEIGHT_REACTIONS})
     public void getPropertyModels_lightweightReactions_filtersByDetailedContentType() {
         setUpChromeProvidedSharingOptionsProviderTest(/*isIncognito=*/false,
                 /*printingEnabled=*/true, LinkGeneration.MAX);
         List<PropertyModel> propertyModels =
                 mChromeProvidedSharingOptionsProvider.getPropertyModels(
-                        ImmutableSet.of(ContentType.IMAGE_AND_LINK),
+                        ImmutableSet.of(ContentType.IMAGE),
                         DetailedContentType.LIGHTWEIGHT_REACTION,
                         /*isMultiWindow=*/false);
 
-        List<String> expectedModels = new ArrayList<>();
-        expectedModels.add(mActivity.getResources().getString(R.string.sharing_copy_image));
-
-        assertCorrectModelsAreInTheRightOrder(propertyModels, expectedModels);
+        assertCorrectModelsAreInTheRightOrder(propertyModels,
+                ImmutableList.of(mActivity.getResources().getString(R.string.sharing_copy_image)));
     }
 
     @Test
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index e1cf8da..6642a3b7 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -96,7 +96,7 @@
 // changed default theme assets, if you need themes to recreate their generated
 // images (which are cached), if you changed how missing values are
 // generated, or if you changed any constants.
-const int kThemePackVersion = 79;
+const int kThemePackVersion = 80;
 
 // IDs that are in the DataPack won't clash with the positive integer
 // uint16_t. kHeaderID should always have the maximum value because we want the
@@ -1061,6 +1061,8 @@
        kColorTabForegroundActiveFrameActive},
       {TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE,
        kColorTabForegroundActiveFrameInactive},
+      {TP::COLOR_TAB_THROBBER_SPINNING, ui::kColorThrobber},
+      {TP::COLOR_TAB_THROBBER_WAITING, ui::kColorThrobberPreconnect},
       {TP::COLOR_TOOLBAR, kColorToolbar},
       {TP::COLOR_TOOLBAR_TEXT, kColorToolbarText},
   };
@@ -1517,9 +1519,20 @@
     SetColorIfUnspecified(TP::COLOR_TOOLBAR_TEXT, toolbar_text_color);
   }
   SkColor toolbar_button_icon_color;
+  color_utils::HSL button_tint;
   if (GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &toolbar_button_icon_color)) {
     SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, toolbar_button_icon_color);
     SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, toolbar_button_icon_color);
+    SetColor(TP::COLOR_TAB_THROBBER_SPINNING, toolbar_button_icon_color);
+    SetColor(TP::COLOR_TAB_THROBBER_WAITING, toolbar_button_icon_color);
+  } else if (GetTint(TP::TINT_BUTTONS, &button_tint)) {
+    // Duplicate how COLOR_TOOLBAR_BUTTON_ICON will be computed.
+    // TODO(pkasting): Should this code be shared with
+    // ThemeHelper::GetDefaultColor() somehow?
+    const SkColor button_color =
+        color_utils::HSLShift(gfx::kChromeIconGrey, button_tint);
+    SetColor(TP::COLOR_TAB_THROBBER_SPINNING, button_color);
+    SetColor(TP::COLOR_TAB_THROBBER_WAITING, button_color);
   }
   SkColor toolbar_text_color;
   if (GetColor(TP::COLOR_TOOLBAR_TEXT, &toolbar_text_color)) {
diff --git a/chrome/browser/themes/theme_helper.cc b/chrome/browser/themes/theme_helper.cc
index 088df4a7..9cb0a6d 100644
--- a/chrome/browser/themes/theme_helper.cc
+++ b/chrome/browser/themes/theme_helper.cc
@@ -419,32 +419,6 @@
     case TP::COLOR_NTP_TEXT_LIGHT:
       return IncreaseLightness(
           GetColor(TP::COLOR_NTP_TEXT, incognito, theme_supplier), 0.40);
-    case TP::COLOR_TAB_THROBBER_SPINNING:
-    case TP::COLOR_TAB_THROBBER_WAITING: {
-      // Similar to the code in BrowserThemeProvider::HasCustomColor(), here we
-      // decide the toolbar button icon has a custom color if the theme supplier
-      // has explicitly specified it or a TINT_BUTTONS value. Unlike that code,
-      // this does not consider TINT_BUTTONS to have been customized just
-      // because it differs from {-1, -1, -1}. The effect is that for the
-      // default light/dark/incognito themes, or custom themes which use the
-      // default toolbar button colors, the default throbber colors will be
-      // used; otherwise the throbber will be colored to match the toolbar
-      // buttons to guarantee visibility.
-      bool has_custom_color = false;
-      const SkColor button_color =
-          GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, incognito, theme_supplier,
-                   &has_custom_color);
-      color_utils::HSL hsl;
-      return (has_custom_color ||
-              (theme_supplier &&
-               theme_supplier->GetTint(TP::TINT_BUTTONS, &hsl)))
-                 ? button_color
-                 : ui::GetAuraColor(
-                       id == TP::COLOR_TAB_THROBBER_SPINNING
-                           ? ui::NativeTheme::kColorId_ThrobberSpinningColor
-                           : ui::NativeTheme::kColorId_ThrobberWaitingColor,
-                       ui::NativeTheme::GetInstanceForNativeUi());
-    }
     case TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE:
     case TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE: {
       return GetColor(id == TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE
diff --git a/chrome/browser/themes/theme_properties.h b/chrome/browser/themes/theme_properties.h
index 5bfc126..8666884 100644
--- a/chrome/browser/themes/theme_properties.h
+++ b/chrome/browser/themes/theme_properties.h
@@ -161,8 +161,8 @@
     COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE_INCOGNITO,
 
     // The throbber colors for tabs or anything on a toolbar (currently, only
-    // the download shelf). If you're adding a throbber elsewhere, such as in
-    // a dialog or bubble, you likely want ui::kColorThrobber.
+    // the download shelf). Do not use directly; only for use inside
+    // browser_theme_pack.cc.
     COLOR_TAB_THROBBER_SPINNING,
     COLOR_TAB_THROBBER_WAITING,
 
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index 7ae009e5..85c62853 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -78,11 +78,13 @@
 std::string ColorIdToString(int id) {
 #define E(color_id, theme_property_id, ...) \
   {theme_property_id, #theme_property_id},
+#define E_CPONLY(color_id)
 
   static constexpr const auto kMap =
       base::MakeFixedFlatMap<int, const char*>({CHROME_COLOR_IDS});
 
 #undef E
+#undef E_CPONLY
   constexpr char kPrefix[] = "ThemeProperties::";
 
   std::string id_str = kMap.find(id)->second;
@@ -327,8 +329,10 @@
 };
 
 #define E(color_id, theme_property_id, ...) theme_property_id,
+#define E_CPONLY(color_id)
 static constexpr int kTestIdValues[] = {CHROME_COLOR_IDS};
 #undef E
+#undef E_CPONLY
 
 INSTANTIATE_TEST_SUITE_P(
     ,
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index adaaca0..bd379a58 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -300,14 +300,6 @@
     "webui/predictors/predictors_ui.h",
     "webui/prefs_internals_source.cc",
     "webui/prefs_internals_source.h",
-    "webui/quota_internals/quota_internals_handler.cc",
-    "webui/quota_internals/quota_internals_handler.h",
-    "webui/quota_internals/quota_internals_proxy.cc",
-    "webui/quota_internals/quota_internals_proxy.h",
-    "webui/quota_internals/quota_internals_types.cc",
-    "webui/quota_internals/quota_internals_types.h",
-    "webui/quota_internals/quota_internals_ui.cc",
-    "webui/quota_internals/quota_internals_ui.h",
     "webui/segmentation_internals/segmentation_internals_page_handler_impl.cc",
     "webui/segmentation_internals/segmentation_internals_page_handler_impl.h",
     "webui/segmentation_internals/segmentation_internals_ui.cc",
@@ -422,7 +414,6 @@
     "//chrome/browser/resources/media:webrtc_logs_resources",
     "//chrome/browser/resources/net_internals:resources",
     "//chrome/browser/resources/omnibox:resources",
-    "//chrome/browser/resources/quota_internals:resources",
     "//chrome/browser/resources/usb_internals:resources",
     "//chrome/browser/safe_browsing",
     "//chrome/browser/ui/webui/bluetooth_internals",
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index f62e567..adeda4e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -840,6 +840,14 @@
     }
 
     /**
+     * Returns whether there are any ongoing animations.
+     */
+    @VisibleForTesting
+    public boolean isAnimationRunningForTesting() {
+        return false;
+    }
+
+    /**
      * Sets the toolbar hairline color, if the toolbar has a hairline below it.
      * @param toolbarColor The toolbar color to base the hairline color on.
      */
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 782ec897..4b2c9e7 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -2869,4 +2869,11 @@
         return getResources().getDimensionPixelSize(R.dimen.location_bar_lateral_padding)
                 - getResources().getDimensionPixelSize(R.dimen.fake_search_box_lateral_padding);
     }
+
+    @Override
+    @VisibleForTesting
+    public boolean isAnimationRunningForTesting() {
+        return mUrlFocusChangeInProgress || mBrandColorTransitionActive
+                || mOptionalButtonAnimationRunning;
+    }
 }
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
index 83a72520..ab143db 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
@@ -108,4 +108,5 @@
 void FakeAccessibilityController::UpdateDictationBubble(
     bool visible,
     ash::DictationBubbleIconType icon,
-    const absl::optional<std::u16string>& text) {}
+    const absl::optional<std::u16string>& text,
+    const absl::optional<std::vector<std::string>>& hints) {}
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
index 29a53c62..b52ac46 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
@@ -70,7 +70,8 @@
   void UpdateDictationBubble(
       bool visible,
       ash::DictationBubbleIconType icon,
-      const absl::optional<std::u16string>& text) override;
+      const absl::optional<std::u16string>& text,
+      const absl::optional<std::vector<std::string>>& hints) override;
 
  private:
   bool was_client_set_ = false;
diff --git a/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.cc b/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.cc
index b8b5f15b..89e4074 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.cc
+++ b/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.cc
@@ -19,6 +19,7 @@
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "extensions/common/constants.h"
 #include "ui/aura/client/aura_constants.h"
+#include "ui/views/widget/native_widget_aura.h"
 
 BrowserAppShelfItemController::BrowserAppShelfItemController(
     const ash::ShelfID& shelf_id,
@@ -31,7 +32,9 @@
   // Registers all running instances that started before this shelf item was
   // created, for example if a running app is later pinned to the shelf.
   registry_.NotifyExistingInstances(this);
-  LoadAppMenuIcon();
+  LoadIcon(extension_misc::EXTENSION_ICON_BITTY,
+           base::BindOnce(&BrowserAppShelfItemController::OnLoadBittyIcon,
+                          weak_ptr_factory_.GetWeakPtr()));
 }
 
 BrowserAppShelfItemController::~BrowserAppShelfItemController() = default;
@@ -88,13 +91,13 @@
       const apps::BrowserWindowInstance* instance =
           registry_.GetBrowserWindowInstanceById(id);
       DCHECK(instance);
-      items.push_back({command_id, instance->window->GetTitle(), menu_icon_});
+      items.push_back({command_id, instance->window->GetTitle(), bitty_icon_});
     } else {
       const apps::BrowserAppInstance* instance =
           registry_.GetAppInstanceById(id);
       DCHECK(instance);
       items.push_back(
-          {command_id, base::UTF8ToUTF16(instance->title), menu_icon_});
+          {command_id, base::UTF8ToUTF16(instance->title), bitty_icon_});
     }
   }
   return items;
@@ -132,6 +135,12 @@
     // Only handle Lacros browser windows.
     return;
   }
+
+  if (!(bitty_icon_.isNull() || medium_icon_.isNull())) {
+    views::NativeWidgetAura::AssignIconToAuraWindow(instance.window,
+                                                    bitty_icon_, medium_icon_);
+  }
+
   int command = ++last_command_id_;
   command_to_instance_map_[command] = instance.id;
 }
@@ -152,6 +161,12 @@
   if (shelf_id().app_id != instance.app_id) {
     return;
   }
+
+  if (!(bitty_icon_.isNull() || medium_icon_.isNull())) {
+    views::NativeWidgetAura::AssignIconToAuraWindow(instance.window,
+                                                    bitty_icon_, medium_icon_);
+  }
+
   int command = ++last_command_id_;
   command_to_instance_map_[command] = instance.id;
 }
@@ -201,7 +216,8 @@
   return it->first;
 }
 
-void BrowserAppShelfItemController::LoadAppMenuIcon() {
+void BrowserAppShelfItemController::LoadIcon(int32_t size_hint_in_dip,
+                                             apps::LoadIconCallback callback) {
   const std::string& app_id = shelf_id().app_id;
   auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
   auto app_type = proxy->AppRegistryCache().GetAppType(app_id);
@@ -210,25 +226,53 @@
         apps::ConvertMojomAppTypToAppType(app_type), app_id,
         apps::IconType::kStandard,
         // matches favicon size
-        /* size_hint_in_dip= */ extension_misc::EXTENSION_ICON_BITTY,
-        /* allow_placeholder_icon= */ false,
-        base::BindOnce(&BrowserAppShelfItemController::OnLoadAppMenuIcon,
-                       weak_ptr_factory_.GetWeakPtr()));
+        /* size_hint_in_dip= */ size_hint_in_dip,
+        /* allow_placeholder_icon= */ false, std::move(callback));
   } else {
     icon_loader_releaser_ = proxy->LoadIcon(
         app_type, app_id, apps::mojom::IconType::kStandard,
         // matches favicon size
-        /* size_hint_in_dip= */ extension_misc::EXTENSION_ICON_BITTY,
+        /* size_hint_in_dip= */ size_hint_in_dip,
         /* allow_placeholder_icon= */ false,
-        apps::MojomIconValueToIconValueCallback(
-            base::BindOnce(&BrowserAppShelfItemController::OnLoadAppMenuIcon,
-                           weak_ptr_factory_.GetWeakPtr())));
+        apps::MojomIconValueToIconValueCallback(std::move(callback)));
   }
 }
 
-void BrowserAppShelfItemController::OnLoadAppMenuIcon(
+void BrowserAppShelfItemController::OnLoadMediumIcon(
     apps::IconValuePtr icon_value) {
   if (icon_value && icon_value->icon_type == apps::IconType::kStandard) {
-    menu_icon_ = icon_value->uncompressed;
+    medium_icon_ = icon_value->uncompressed;
+
+    // At this point, we have loaded both icons needed to assign an icon to the
+    // Lacros and Ash windows, so we can assign the icons to the instances that
+    // have already been created.
+    std::string app_id = shelf_id().app_id;
+    if (app_id == extension_misc::kLacrosAppId) {
+      for (auto* instance : registry_.GetLacrosBrowserWindowInstances()) {
+        views::NativeWidgetAura::AssignIconToAuraWindow(
+            instance->window, bitty_icon_, medium_icon_);
+      }
+    } else {
+      for (auto* instance : registry_.SelectAppInstances(
+               [&app_id](const apps::BrowserAppInstance& instance) {
+                 return instance.type ==
+                            apps::BrowserAppInstance::Type::kAppWindow &&
+                        app_id == instance.app_id;
+               })) {
+        views::NativeWidgetAura::AssignIconToAuraWindow(
+            instance->window, bitty_icon_, medium_icon_);
+      }
+    }
+  }
+}
+
+void BrowserAppShelfItemController::OnLoadBittyIcon(
+    apps::IconValuePtr icon_value) {
+  if (icon_value && icon_value->icon_type == apps::IconType::kStandard) {
+    bitty_icon_ = icon_value->uncompressed;
+    BrowserAppShelfItemController::LoadIcon(
+        extension_misc::EXTENSION_ICON_MEDIUM,
+        base::BindOnce(&BrowserAppShelfItemController::OnLoadMediumIcon,
+                       weak_ptr_factory_.GetWeakPtr()));
   }
 }
diff --git a/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.h b/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.h
index 4574027..f27aa41 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.h
+++ b/chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.h
@@ -71,8 +71,9 @@
   // Gets the command ID for this item. The item must exist.
   int GetInstanceCommand(const base::UnguessableToken& id);
 
-  void LoadAppMenuIcon();
-  void OnLoadAppMenuIcon(apps::IconValuePtr icon_value);
+  void LoadIcon(int32_t size_hint_in_dip, apps::LoadIconCallback callback);
+  void OnLoadMediumIcon(apps::IconValuePtr icon_value);
+  void OnLoadBittyIcon(apps::IconValuePtr icon_value);
 
   Profile* profile_;
   apps::BrowserAppInstanceRegistry& registry_;
@@ -82,7 +83,8 @@
   std::unique_ptr<ShelfContextMenu> context_menu_;
 
   std::unique_ptr<apps::IconLoader::Releaser> icon_loader_releaser_;
-  gfx::ImageSkia menu_icon_;
+  gfx::ImageSkia medium_icon_;
+  gfx::ImageSkia bitty_icon_;
 
   // Map of app menu item command IDs to instance IDs, used to maintain a stable
   // association of instances to command IDs and to order the items by launch
diff --git a/chrome/browser/ui/color/BUILD.gn b/chrome/browser/ui/color/BUILD.gn
index 08e8c46..6e1aa7cd 100644
--- a/chrome/browser/ui/color/BUILD.gn
+++ b/chrome/browser/ui/color/BUILD.gn
@@ -12,6 +12,8 @@
 
 source_set("mixers") {
   sources = [
+    "autofill_color_mixer.cc",
+    "autofill_color_mixer.h",
     "chrome_color_mixer.cc",
     "chrome_color_mixer.h",
     "chrome_color_mixers.cc",
@@ -26,6 +28,7 @@
 
   deps = [
     ":color_headers",
+    "//build:branding_buildflags",
     "//ui/color:color",
     "//ui/color:mixers",
   ]
diff --git a/chrome/browser/ui/color/autofill_color_mixer.cc b/chrome/browser/ui/color/autofill_color_mixer.cc
new file mode 100644
index 0000000..324427ce
--- /dev/null
+++ b/chrome/browser/ui/color/autofill_color_mixer.cc
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium 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/ui/color/autofill_color_mixer.h"
+
+#include "build/branding_buildflags.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
+#include "ui/color/color_id.h"
+#include "ui/color/color_mixer.h"
+#include "ui/color/color_provider.h"
+#include "ui/color/color_provider_manager.h"
+#include "ui/color/color_recipe.h"
+#include "ui/color/color_transform.h"
+#include "ui/gfx/color_palette.h"
+
+void AddAutofillColorMixer(ui::ColorProvider* provider,
+                           const ui::ColorProviderManager::Key& key) {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  ui::ColorMixer& mixer = provider->AddMixer();
+
+  const bool dark_mode =
+      key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
+  mixer[kColorGooglePayLogo] = {dark_mode ? SK_ColorWHITE
+                                          : gfx::kGoogleGrey700};
+#endif
+}
diff --git a/chrome/browser/ui/color/autofill_color_mixer.h b/chrome/browser/ui/color/autofill_color_mixer.h
new file mode 100644
index 0000000..8868d20e
--- /dev/null
+++ b/chrome/browser/ui/color/autofill_color_mixer.h
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium 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_UI_COLOR_AUTOFILL_COLOR_MIXER_H_
+#define CHROME_BROWSER_UI_COLOR_AUTOFILL_COLOR_MIXER_H_
+
+#include "ui/color/color_provider_manager.h"
+
+namespace ui {
+class ColorProvider;
+}
+
+// Adds a color mixer that contains recipes for autofill colors to |provider|
+// with |key|.
+void AddAutofillColorMixer(ui::ColorProvider* provider,
+                           const ui::ColorProviderManager::Key& key);
+
+#endif  // CHROME_BROWSER_UI_COLOR_AUTOFILL_COLOR_MIXER_H_
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 2212fd6..7bb3724d 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -20,6 +20,8 @@
     ThemeProperties::COLOR_DOWNLOAD_SHELF_BUTTON_BACKGROUND) \
   E(kColorDownloadShelfButtonText, \
     ThemeProperties::COLOR_DOWNLOAD_SHELF_BUTTON_TEXT) \
+  /* Google branding colors. */ \
+  E_CPONLY(kColorGooglePayLogo) \
   /* Omnibox output colors. */ \
   E(kColorOmniboxBackground, ThemeProperties::COLOR_OMNIBOX_BACKGROUND) \
   E(kColorOmniboxBackgroundHovered, \
diff --git a/chrome/browser/ui/color/chrome_color_mixers.cc b/chrome/browser/ui/color/chrome_color_mixers.cc
index f3549552..b4140c7b 100644
--- a/chrome/browser/ui/color/chrome_color_mixers.cc
+++ b/chrome/browser/ui/color/chrome_color_mixers.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/color/chrome_color_mixers.h"
 
+#include "chrome/browser/ui/color/autofill_color_mixer.h"
 #include "chrome/browser/ui/color/chrome_color_mixer.h"
 #include "chrome/browser/ui/color/native_chrome_color_mixer.h"
 #include "chrome/browser/ui/color/omnibox_color_mixer.h"
@@ -12,6 +13,8 @@
                           const ui::ColorProviderManager::Key& key) {
   AddChromeColorMixer(provider, key);
   AddNativeChromeColorMixer(provider, key);
+
+  AddAutofillColorMixer(provider, key);
   AddOmniboxColorMixer(provider, key);
 
   if (key.custom_theme) {
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc
new file mode 100644
index 0000000..391a0f9c
--- /dev/null
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc
@@ -0,0 +1,50 @@
+// 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/sharesheet/sharesheet_service.h"
+#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/page_action/page_action_icon_type.h"
+#include "chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class SharingHubBubbleControllerChromeOsBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  SharingHubBubbleControllerChromeOsBrowserTest() = default;
+  ~SharingHubBubbleControllerChromeOsBrowserTest() override = default;
+};
+
+IN_PROC_BROWSER_TEST_F(SharingHubBubbleControllerChromeOsBrowserTest,
+                       OpenSharesheet) {
+  sharesheet::SharesheetService* sharesheet_service =
+      sharesheet::SharesheetServiceFactory::GetForProfile(browser()->profile());
+  gfx::NativeWindow web_contents_containing_window_ =
+      browser()
+          ->tab_strip_model()
+          ->GetActiveWebContents()
+          ->GetTopLevelNativeWindow();
+
+  // Open the sharesheet using the sharing hub controller.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  sharing_hub::SharingHubBubbleController::CreateOrGetFromWebContents(
+      web_contents)
+      ->ShowBubble();
+
+  // Wait until the sharesheet is fully opened.
+  base::RunLoop().RunUntilIdle();
+
+  // Verify that the sharesheet is open.
+  sharesheet::SharesheetController* controller =
+      sharesheet_service->GetSharesheetController(
+          web_contents_containing_window_);
+  ASSERT_TRUE(controller->IsBubbleVisible());
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
index d522f31..86a1783 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
@@ -14,6 +14,7 @@
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/views/autofill/payments/dialog_view_ids.h"
 #include "chrome/browser/ui/views/autofill/payments/payments_view_util.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -103,17 +104,18 @@
       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
       ChromeLayoutProvider::Get()->GetDistanceMetric(
           DISTANCE_RELATED_CONTROL_VERTICAL_SMALL)));
-  const SkColor color = GetColorProvider()->GetColor(ui::kColorIcon);
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   // kGooglePayLogoIcon is square, and CreateTiledImage() will clip it whereas
   // setting the icon size would rescale it incorrectly.
   gfx::ImageSkia image = gfx::ImageSkiaOperations::CreateTiledImage(
-      gfx::CreateVectorIcon(kGooglePayLogoIcon, color),
+      gfx::CreateVectorIcon(kGooglePayLogoIcon,
+                            GetColorProvider()->GetColor(kColorGooglePayLogo)),
       /*x=*/0, /*y=*/0, kMigrationBubbleGooglePayLogoWidth,
       kMigrationBubbleGooglePayLogoHeight);
 #else
   gfx::ImageSkia image = gfx::CreateVectorIcon(
-      kCreditCardIcon, kMigrationBubbleGooglePayLogoHeight, color);
+      kCreditCardIcon, kMigrationBubbleGooglePayLogoHeight,
+      GetColorProvider()->GetColor(ui::kColorIcon));
 #endif
   views::ImageView* icon_view = new views::ImageView();
   icon_view->SetImage(image);
diff --git a/chrome/browser/ui/views/autofill/payments/payments_view_util.cc b/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
index 9f9a0004..053de998 100644
--- a/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
+++ b/chrome/browser/ui/views/autofill/payments/payments_view_util.cc
@@ -8,6 +8,7 @@
 #include "base/ranges/algorithm.h"
 #include "build/branding_buildflags.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "components/strings/grit/components_strings.h"
@@ -61,7 +62,6 @@
     ImageView::OnThemeChanged();
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
     gfx::ImageSkia image;
-    const SkColor color = GetColorProvider()->GetColor(ui::kColorIcon);
     switch (icon_to_show_) {
       case TitleWithIconAndSeparatorView::Icon::GOOGLE_PAY:
         // kGooglePayLogoIcon is square overall, despite the drawn portion being
@@ -69,11 +69,15 @@
         // it whereas setting the icon size would rescale it incorrectly and
         // keep the bottom empty portion.
         image = gfx::ImageSkiaOperations::CreateTiledImage(
-            gfx::CreateVectorIcon(kGooglePayLogoIcon, color),
+            gfx::CreateVectorIcon(
+                kGooglePayLogoIcon,
+                GetColorProvider()->GetColor(kColorGooglePayLogo)),
             /*x=*/0, /*y=*/0, kGooglePayLogoWidth, kIconHeight);
         break;
       case TitleWithIconAndSeparatorView::Icon::GOOGLE_G:
-        image = gfx::CreateVectorIcon(kGoogleGLogoIcon, kIconHeight, color);
+        image =
+            gfx::CreateVectorIcon(kGoogleGLogoIcon, kIconHeight,
+                                  GetColorProvider()->GetColor(ui::kColorIcon));
         break;
     }
 
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
index 94af522..0e0b5e18 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -77,8 +77,7 @@
   const gfx::VectorIcon* new_icon;
   SkColor icon_color;
   if (icon_state_ == download::DownloadIconState::kProgress) {
-    icon_color = GetThemeProvider()->GetColor(
-        ThemeProperties::COLOR_TAB_THROBBER_SPINNING);
+    icon_color = GetColorProvider()->GetColor(ui::kColorThrobber);
     new_icon = &kDownloadInProgressIcon;
   } else {
     icon_color = GetThemeProvider()->GetColor(
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index cebecb12..09d3283 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -1062,8 +1062,7 @@
     const gfx::RectF& bounds,
     const base::TimeDelta& indeterminate_progress_time,
     int percent_done) const {
-  const SkColor color = GetThemeProvider()->GetColor(
-      ThemeProperties::COLOR_TAB_THROBBER_SPINNING);
+  const SkColor color = GetColorProvider()->GetColor(ui::kColorThrobber);
 
   // Draw background.
   cc::PaintFlags bg_flags;
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_loading_indicator_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_loading_indicator_view.cc
index 5e76f52..75c22b6 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_loading_indicator_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_loading_indicator_view.cc
@@ -49,8 +49,7 @@
   if (!throbber_start_time_)
     return;
 
-  const SkColor color = GetThemeProvider()->GetColor(
-      ThemeProperties::COLOR_TAB_THROBBER_SPINNING);
+  const SkColor color = GetColorProvider()->GetColor(ui::kColorThrobber);
   constexpr int kThrobberStrokeWidth = 2;
   gfx::PaintThrobberSpinning(canvas, GetLocalBounds(), color,
                              base::TimeTicks::Now() - *throbber_start_time_,
diff --git a/chrome/browser/ui/views/payments/payment_request_retry_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_retry_browsertest.cc
index d2cc36e..5711aae 100644
--- a/chrome/browser/ui/views/payments/payment_request_retry_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_retry_browsertest.cc
@@ -96,4 +96,64 @@
   EXPECT_EQ(nullptr, add_card_button);
 }
 
+// The tests in this class correspond to the tests of the same name in
+// PaymentRequestRetryTest, with basic-card disabled.
+// Parameterized tests are not used because the test setup for both tests are
+// too different.
+class PaymentRequestRetryBasicCardDisabledTest
+    : public PaymentRequestBrowserTestBase {
+ protected:
+  PaymentRequestRetryBasicCardDisabledTest() {
+    feature_list_.InitAndDisableFeature(::features::kPaymentRequestBasicCard);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestRetryBasicCardDisabledTest,
+                       DoNotAllowPaymentInstrumentChange) {
+  // Installs two apps so that the Payment Request UI will be shown.
+  std::string a_method_name;
+  InstallPaymentApp("a.com", "payment_request_success_responder.js",
+                    &a_method_name);
+  std::string b_method_name;
+  InstallPaymentApp("b.com", "payment_request_success_responder.js",
+                    &b_method_name);
+
+  NavigateTo("/payment_request_retry_with_payer_errors.html");
+  autofill::AutofillProfile contact = autofill::test::GetFullProfile();
+  AddAutofillProfile(contact);
+
+  // Confirm that there are two payment apps available.
+  InvokePaymentRequestUIWithJs(
+      content::JsReplace("buyWithMethods([{supportedMethods:$1}"
+                         ", {supportedMethods:$2}]);",
+                         a_method_name, b_method_name));
+  PaymentRequest* request = GetPaymentRequests().front();
+  EXPECT_EQ(2U, request->state()->available_apps().size());
+
+  // Click on pay.
+  EXPECT_TRUE(IsPayButtonEnabled());
+  ResetEventWaiterForSequence({DialogEvent::PROCESSING_SPINNER_SHOWN});
+  ClickOnDialogViewAndWait(DialogViewID::PAY_BUTTON, dialog_view());
+
+  // Confirm that only one payment app is available for retry().
+  ResetEventWaiterForSequence({DialogEvent::PROCESSING_SPINNER_HIDDEN,
+                               DialogEvent::SPEC_DONE_UPDATING,
+                               DialogEvent::PROCESSING_SPINNER_HIDDEN,
+                               DialogEvent::BACK_TO_PAYMENT_SHEET_NAVIGATION,
+                               DialogEvent::CONTACT_INFO_EDITOR_OPENED});
+  ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(),
+                                     "retry({"
+                                     "  payer: {"
+                                     "    email: 'EMAIL ERROR',"
+                                     "    name: 'NAME ERROR',"
+                                     "    phone: 'PHONE ERROR'"
+                                     "  }"
+                                     "});"));
+  WaitForObservedEvent();
+  EXPECT_EQ(1U, request->state()->available_apps().size());
+}
+
 }  // namespace payments
diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
index 2cdf2080..866022f 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
@@ -86,44 +86,6 @@
   EXPECT_EQ(context(), help_bubble_->GetContext());
 }
 
-// Note: if this test is flaky, it may need to be moved to an interactive UI
-// test instead; please contact dfried@.
-// TODO(crbug/1290983): Disabled because of failures on Mac.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_ToggleFocusForAccessibility DISABLED_ToggleFocusForAccessibility
-#else
-#define MAYBE_ToggleFocusForAccessibility ToggleFocusForAccessibility
-#endif
-IN_PROC_BROWSER_TEST_F(HelpBubbleFactoryViewsBrowsertest,
-                       MAYBE_ToggleFocusForAccessibility) {
-  HelpBubbleParams params;
-  params.body_text = u"Hello world!";
-  HelpBubbleButtonParams button_params;
-  button_params.text = u"Button";
-  button_params.is_default = true;
-  params.buttons.emplace_back(std::move(button_params));
-  help_bubble_ =
-      registry()->CreateHelpBubble(GetAnchorElement(), std::move(params));
-  HelpBubbleView* const bubble_view =
-      help_bubble_->AsA<HelpBubbleViews>()->bubble_view();
-
-  // Toggle focus to the help widget and then wait for it to be focused.
-  {
-    WidgetFocusWaiter waiter(bubble_view->GetWidget());
-    waiter.WaitAfter(base::BindLambdaForTesting(
-        [&]() { help_bubble_->ToggleFocusForAccessibility(); }));
-    EXPECT_TRUE(bubble_view->GetButtonForTesting(0)->HasFocus());
-  }
-
-  // Toggle focus to the anchor view and wait for it to become focused.
-  {
-    WidgetFocusWaiter waiter(GetAnchorElement()->view()->GetWidget());
-    waiter.WaitAfter(base::BindLambdaForTesting(
-        [&]() { help_bubble_->ToggleFocusForAccessibility(); }));
-    EXPECT_TRUE(GetAnchorElement()->view()->HasFocus());
-  }
-}
-
 // Note: if this test is flaky (especially the final EXPECT_TRUE) it may be
 // that the browser window completely fills the test display and expanding it
 // does not work. Please look at the error message reported and make
diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_views_interactive_uitest.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_views_interactive_uitest.cc
new file mode 100644
index 0000000..2467ccb
--- /dev/null
+++ b/chrome/browser/ui/views/user_education/help_bubble_factory_views_interactive_uitest.cc
@@ -0,0 +1,84 @@
+// 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 "base/callback_forward.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/browser/ui/user_education/help_bubble_factory_registry.h"
+#include "chrome/browser/ui/user_education/help_bubble_params.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/browser/ui/views/user_education/help_bubble_factory_views.h"
+#include "chrome/browser/ui/views/user_education/help_bubble_view.h"
+#include "chrome/browser/ui/views/user_education/user_education_test_util.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "ui/base/interaction/element_identifier.h"
+#include "ui/base/interaction/element_tracker.h"
+#include "ui/views/interaction/element_tracker_views.h"
+#include "ui/views/test/widget_test.h"
+
+class HelpBubbleFactoryViewsUiTest : public InProcessBrowserTest {
+ public:
+ protected:
+  ui::ElementContext context() {
+    return browser()->window()->GetElementContext();
+  }
+
+  HelpBubbleFactoryRegistry* registry() {
+    return BrowserView::GetBrowserViewForBrowser(browser())
+        ->GetFeaturePromoController()
+        ->bubble_factory_registry();
+  }
+
+  views::TrackedElementViews* GetAnchorElement() {
+    return views::ElementTrackerViews::GetInstance()->GetElementForView(
+        BrowserView::GetBrowserViewForBrowser(browser())
+            ->toolbar()
+            ->app_menu_button());
+  }
+};
+
+// Moved to interactive uitests due to crbug.com/1290983 (widget activation is
+// not reliable when running alongside other tests).
+IN_PROC_BROWSER_TEST_F(HelpBubbleFactoryViewsUiTest,
+                       ToggleFocusForAccessibility) {
+  HelpBubbleParams params;
+  params.body_text = u"Hello world!";
+  HelpBubbleButtonParams button_params;
+  button_params.text = u"Button";
+  button_params.is_default = true;
+  params.buttons.emplace_back(std::move(button_params));
+
+  std::unique_ptr<HelpBubble> help_bubble =
+      registry()->CreateHelpBubble(GetAnchorElement(), std::move(params));
+  HelpBubbleView* const bubble_view =
+      help_bubble->AsA<HelpBubbleViews>()->bubble_view();
+
+  // Toggle focus to the help widget and then wait for it to be focused.
+  {
+    WidgetFocusWaiter waiter(bubble_view->GetWidget());
+    waiter.WaitAfter(base::BindLambdaForTesting(
+        [&]() { help_bubble->ToggleFocusForAccessibility(); }));
+    EXPECT_TRUE(bubble_view->GetButtonForTesting(0)->HasFocus());
+  }
+
+  // Toggle focus to the anchor view and wait for it to become focused.
+#if BUILDFLAG(IS_MAC)
+  // Widget activation is a little wonky on Mac, so we'll just ensure that the
+  // anchor element is correctly focused.
+  help_bubble->ToggleFocusForAccessibility();
+#else
+  // On non-Mac platforms, we can safely wait for widget focus to return to the
+  // browser window.
+  {
+    WidgetFocusWaiter waiter(GetAnchorElement()->view()->GetWidget());
+    waiter.WaitAfter(base::BindLambdaForTesting(
+        [&]() { help_bubble->ToggleFocusForAccessibility(); }));
+  }
+#endif
+  EXPECT_TRUE(GetAnchorElement()->view()->HasFocus());
+}
diff --git a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
index 01187d1e..425869ec9 100644
--- a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
+++ b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
@@ -257,7 +257,6 @@
     "chrome://print",
     "chrome://process-internals",
     "chrome://quota-internals",
-    "chrome://quota-internals-2",
     "chrome://reset-password",
     "chrome://safe-browsing",
     "chrome://serviceworker-internals",
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 6d07380..7450826 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -54,7 +54,6 @@
 #include "chrome/browser/ui/webui/omnibox/omnibox_ui.h"
 #include "chrome/browser/ui/webui/policy/policy_ui.h"
 #include "chrome/browser/ui/webui/predictors/predictors_ui.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_ui.h"
 #include "chrome/browser/ui/webui/segmentation_internals/segmentation_internals_ui.h"
 #include "chrome/browser/ui/webui/signin_internals_ui.h"
 #include "chrome/browser/ui/webui/sync_internals/sync_internals_ui.h"
@@ -211,6 +210,7 @@
 #include "chrome/browser/ash/web_applications/chrome_file_manager_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/help_app/help_app_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_user_provider_impl.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h"
@@ -589,15 +589,17 @@
 template <>
 WebUIController* NewWebUI<ash::PersonalizationAppUI>(WebUI* web_ui,
                                                      const GURL& url) {
+  auto ambient_provider =
+      std::make_unique<PersonalizationAppAmbientProviderImpl>(web_ui);
   auto theme_provider =
       std::make_unique<PersonalizationAppThemeProviderImpl>(web_ui);
   auto user_provider =
       std::make_unique<PersonalizationAppUserProviderImpl>(web_ui);
   auto wallpaper_provider =
       std::make_unique<PersonalizationAppWallpaperProviderImpl>(web_ui);
-  return new ash::PersonalizationAppUI(web_ui, std::move(theme_provider),
-                                       std::move(user_provider),
-                                       std::move(wallpaper_provider));
+  return new ash::PersonalizationAppUI(
+      web_ui, std::move(ambient_provider), std::move(theme_provider),
+      std::move(user_provider), std::move(wallpaper_provider));
 }
 
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -707,8 +709,6 @@
     return &NewWebUI<PasswordManagerInternalsUI>;
   if (url.host_piece() == chrome::kChromeUIPredictorsHost)
     return &NewWebUI<PredictorsUI>;
-  if (url.host_piece() == chrome::kChromeUIQuotaInternalsHost)
-    return &NewWebUI<QuotaInternalsUI>;
   if (url.host_piece() == safe_browsing::kChromeUISafeBrowsingHost)
     return &NewWebUI<safe_browsing::SafeBrowsingUI>;
   if (url.host_piece() == chrome::kChromeUISegmentationInternalsHost)
diff --git a/chrome/browser/ui/webui/quota_internals/DIR_METADATA b/chrome/browser/ui/webui/quota_internals/DIR_METADATA
deleted file mode 100644
index 4bba49e..0000000
--- a/chrome/browser/ui/webui/quota_internals/DIR_METADATA
+++ /dev/null
@@ -1,4 +0,0 @@
-monorail {
-  component: "Blink>Storage>Quota"
-}
-team_email: "storage-dev@chromium.org"
diff --git a/chrome/browser/ui/webui/quota_internals/OWNERS b/chrome/browser/ui/webui/quota_internals/OWNERS
deleted file mode 100644
index a61a714..0000000
--- a/chrome/browser/ui/webui/quota_internals/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://storage/browser/quota/OWNERS
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_handler.cc b/chrome/browser/ui/webui/quota_internals/quota_internals_handler.cc
deleted file mode 100644
index d0322042..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_handler.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2011 The Chromium 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/ui/webui/quota_internals/quota_internals_handler.h"
-
-#include <string>
-
-#include "base/bind.h"
-#include "base/feature_list.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_types.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/browser/web_ui.h"
-#include "content/public/common/content_features.h"
-
-using content::BrowserContext;
-
-namespace {
-
-bool IsStoragePressureEnabled() {
-#if BUILDFLAG(IS_ANDROID)
-  return false;
-#else
-  return true;
-#endif
-}
-
-}  // namespace
-
-namespace quota_internals {
-
-QuotaInternalsHandler::QuotaInternalsHandler() {}
-
-QuotaInternalsHandler::~QuotaInternalsHandler() {
-  if (proxy_.get())
-    proxy_->handler_ = nullptr;
-}
-
-void QuotaInternalsHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
-      "requestInfo", base::BindRepeating(&QuotaInternalsHandler::OnRequestInfo,
-                                         base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
-      "triggerStoragePressure",
-      base::BindRepeating(&QuotaInternalsHandler::OnTriggerStoragePressure,
-                          base::Unretained(this)));
-}
-
-void QuotaInternalsHandler::ReportAvailableSpace(int64_t available_space) {
-  FireWebUIListener("AvailableSpaceUpdated",
-                    base::Value(static_cast<double>(available_space)));
-}
-
-void QuotaInternalsHandler::ReportGlobalInfo(const GlobalStorageInfo& data) {
-  base::Value value(data.NewValue());
-  FireWebUIListener("GlobalInfoUpdated", value);
-}
-
-void QuotaInternalsHandler::ReportPerHostInfo(
-    const std::vector<PerHostStorageInfo>& hosts) {
-  base::ListValue values;
-  for (auto itr(hosts.begin()); itr != hosts.end(); ++itr) {
-    values.Append(itr->NewValue());
-  }
-
-  FireWebUIListener("PerHostInfoUpdated", values);
-}
-
-void QuotaInternalsHandler::ReportPerOriginInfo(
-    const std::vector<PerOriginStorageInfo>& origins) {
-  base::ListValue origins_value;
-  for (auto itr(origins.begin()); itr != origins.end(); ++itr) {
-    origins_value.Append(itr->NewValue());
-  }
-
-  FireWebUIListener("PerOriginInfoUpdated", origins_value);
-}
-
-void QuotaInternalsHandler::ReportStatistics(const Statistics& stats) {
-  base::DictionaryValue dict;
-  for (auto itr(stats.begin()); itr != stats.end(); ++itr) {
-    dict.SetStringKey(itr->first, itr->second);
-  }
-
-  FireWebUIListener("StatisticsUpdated", dict);
-}
-
-void QuotaInternalsHandler::ReportStoragePressureFlag() {
-  base::DictionaryValue flag_enabled;
-  flag_enabled.SetBoolean("isStoragePressureEnabled",
-                          IsStoragePressureEnabled());
-  FireWebUIListener("StoragePressureFlagUpdated", flag_enabled);
-}
-
-void QuotaInternalsHandler::OnRequestInfo(const base::ListValue*) {
-  AllowJavascript();
-  if (!proxy_.get())
-    proxy_ = new QuotaInternalsProxy(this);
-  ReportStoragePressureFlag();
-  proxy_->RequestInfo(Profile::FromWebUI(web_ui())
-                          ->GetDefaultStoragePartition()
-                          ->GetQuotaManager());
-}
-
-void QuotaInternalsHandler::OnTriggerStoragePressure(
-    const base::ListValue* args) {
-  AllowJavascript();
-  CHECK_EQ(1U, args->GetList().size());
-  const std::string& origin_string = args->GetList()[0].GetString();
-  GURL url(origin_string);
-
-  if (!proxy_.get())
-    proxy_ = new QuotaInternalsProxy(this);
-  proxy_->TriggerStoragePressure(url::Origin::Create(url),
-                                 Profile::FromWebUI(web_ui())
-                                     ->GetDefaultStoragePartition()
-                                     ->GetQuotaManager());
-}
-
-}  // namespace quota_internals
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_handler.h b/chrome/browser/ui/webui/quota_internals/quota_internals_handler.h
deleted file mode 100644
index 925d8075..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_handler.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2011 The Chromium 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_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_HANDLER_H_
-
-#include <stdint.h>
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/memory/ref_counted.h"
-#include "content/public/browser/web_ui_message_handler.h"
-
-namespace base {
-class ListValue;
-}
-
-namespace quota_internals {
-
-class QuotaInternalsProxy;
-class GlobalStorageInfo;
-class PerHostStorageInfo;
-class PerOriginStorageInfo;
-typedef std::map<std::string, std::string> Statistics;
-
-// This class handles message from WebUI page of chrome://quota-internals/.
-// All methods in this class should be called on UI thread.
-class QuotaInternalsHandler : public content::WebUIMessageHandler {
- public:
-  QuotaInternalsHandler();
-
-  QuotaInternalsHandler(const QuotaInternalsHandler&) = delete;
-  QuotaInternalsHandler& operator=(const QuotaInternalsHandler&) = delete;
-
-  ~QuotaInternalsHandler() override;
-  void RegisterMessages() override;
-
-  // Called by QuotaInternalsProxy to report information to WebUI page.
-  void ReportAvailableSpace(int64_t available_space);
-  void ReportGlobalInfo(const GlobalStorageInfo& data);
-  void ReportPerHostInfo(const std::vector<PerHostStorageInfo>& hosts);
-  void ReportPerOriginInfo(const std::vector<PerOriginStorageInfo>& origins);
-  void ReportStatistics(const Statistics& stats);
-  void ReportStoragePressureFlag();
-
- private:
-  void OnRequestInfo(const base::ListValue*);
-  void OnTriggerStoragePressure(const base::ListValue*);
-
-  scoped_refptr<QuotaInternalsProxy> proxy_;
-};
-}  // namespace quota_internals
-
-#endif  // CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc b/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc
deleted file mode 100644
index 7125d3d..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h"
-
-#include <set>
-#include <string>
-
-#include "base/bind.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_handler.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_types.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom-forward.h"
-#include "url/origin.h"
-
-using blink::mojom::StorageType;
-using content::BrowserThread;
-
-namespace quota_internals {
-
-QuotaInternalsProxy::QuotaInternalsProxy(QuotaInternalsHandler* handler)
-    : handler_(handler) {}
-
-void QuotaInternalsProxy::RequestInfo(
-    scoped_refptr<storage::QuotaManager> quota_manager) {
-  DCHECK(quota_manager.get());
-  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
-    content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&QuotaInternalsProxy::RequestInfo, this, quota_manager));
-    return;
-  }
-  quota_manager_ = quota_manager;
-
-  quota_manager_->GetQuotaSettings(base::BindOnce(
-      &QuotaInternalsProxy::DidGetSettings, weak_factory_.GetWeakPtr()));
-
-  quota_manager_->GetStorageCapacity(base::BindOnce(
-      &QuotaInternalsProxy::DidGetCapacity, weak_factory_.GetWeakPtr()));
-
-  quota_manager_->GetGlobalUsage(
-      StorageType::kTemporary,
-      base::BindOnce(&QuotaInternalsProxy::DidGetGlobalUsage,
-                     weak_factory_.GetWeakPtr(), StorageType::kTemporary));
-
-  quota_manager_->GetGlobalUsage(
-      StorageType::kPersistent,
-      base::BindOnce(&QuotaInternalsProxy::DidGetGlobalUsage,
-                     weak_factory_.GetWeakPtr(), StorageType::kPersistent));
-
-  quota_manager_->GetGlobalUsage(
-      StorageType::kSyncable,
-      base::BindOnce(&QuotaInternalsProxy::DidGetGlobalUsage,
-                     weak_factory_.GetWeakPtr(), StorageType::kSyncable));
-
-  quota_manager_->GetStorageKeysForType(
-      StorageType::kTemporary,
-      base::BindOnce(&QuotaInternalsProxy::DidGetStorageKeys,
-                     weak_factory_.GetWeakPtr(), StorageType::kTemporary));
-
-  quota_manager_->GetStorageKeysForType(
-      StorageType::kPersistent,
-      base::BindOnce(&QuotaInternalsProxy::DidGetStorageKeys,
-                     weak_factory_.GetWeakPtr(), StorageType::kPersistent));
-
-  quota_manager_->GetStorageKeysForType(
-      StorageType::kSyncable,
-      base::BindOnce(&QuotaInternalsProxy::DidGetStorageKeys,
-                     weak_factory_.GetWeakPtr(), StorageType::kSyncable));
-
-  quota_manager_->DumpQuotaTable(base::BindOnce(
-      &QuotaInternalsProxy::DidDumpQuotaTable, weak_factory_.GetWeakPtr()));
-
-  quota_manager_->DumpBucketTable(base::BindOnce(
-      &QuotaInternalsProxy::DidDumpBucketTable, weak_factory_.GetWeakPtr()));
-
-  quota_manager_->GetStatistics(base::BindOnce(
-      &QuotaInternalsProxy::DidGetStatistics, weak_factory_.GetWeakPtr()));
-}
-
-void QuotaInternalsProxy::TriggerStoragePressure(
-    url::Origin origin,
-    scoped_refptr<storage::QuotaManager> quota_manager) {
-  DCHECK(quota_manager.get());
-  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
-    content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(&QuotaInternalsProxy::TriggerStoragePressure,
-                                  this, origin, quota_manager));
-    return;
-  }
-  quota_manager->SimulateStoragePressure(blink::StorageKey(origin));
-}
-
-QuotaInternalsProxy::~QuotaInternalsProxy() = default;
-
-#define RELAY_TO_HANDLER(func, arg_t)                                        \
-  void QuotaInternalsProxy::func(arg_t arg) {                                \
-    if (!handler_)                                                           \
-      return;                                                                \
-    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {                    \
-      content::GetUIThreadTaskRunner({})->PostTask(                          \
-          FROM_HERE, base::BindOnce(&QuotaInternalsProxy::func, this, arg)); \
-      return;                                                                \
-    }                                                                        \
-                                                                             \
-    handler_->func(arg);                                                     \
-  }
-
-RELAY_TO_HANDLER(ReportAvailableSpace, int64_t)
-RELAY_TO_HANDLER(ReportGlobalInfo, const GlobalStorageInfo&)
-RELAY_TO_HANDLER(ReportPerHostInfo, const std::vector<PerHostStorageInfo>&)
-RELAY_TO_HANDLER(ReportPerOriginInfo, const std::vector<PerOriginStorageInfo>&)
-RELAY_TO_HANDLER(ReportStatistics, const Statistics&)
-
-#undef RELAY_TO_HANDLER
-
-void QuotaInternalsProxy::DidGetSettings(
-    const storage::QuotaSettings& settings) {
-  // TODO(michaeln): also report the other config fields
-  GlobalStorageInfo info(StorageType::kTemporary);
-  info.set_quota(settings.pool_size);
-  ReportGlobalInfo(info);
-}
-
-void QuotaInternalsProxy::DidGetCapacity(int64_t total_space,
-                                         int64_t available_space) {
-  // TODO(michaeln): also report total_space
-  ReportAvailableSpace(available_space);
-}
-
-void QuotaInternalsProxy::DidGetGlobalUsage(StorageType type,
-                                            int64_t usage,
-                                            int64_t unlimited_usage) {
-  GlobalStorageInfo info(type);
-  info.set_usage(usage);
-  info.set_unlimited_usage(unlimited_usage);
-
-  ReportGlobalInfo(info);
-}
-
-void QuotaInternalsProxy::DidGetStorageKeys(
-    StorageType type,
-    const std::set<blink::StorageKey>& storage_keys) {
-  std::vector<PerOriginStorageInfo> origin_infos;
-  origin_infos.reserve(storage_keys.size());
-
-  std::set<std::string> hosts;
-  std::vector<PerHostStorageInfo> host_infos;
-
-  for (const blink::StorageKey& storage_key : storage_keys) {
-    PerOriginStorageInfo per_origin_info(storage_key.origin().GetURL(), type);
-    origin_infos.push_back(per_origin_info);
-
-    const std::string& host = storage_key.origin().host();
-    if (hosts.insert(host).second) {
-      PerHostStorageInfo per_host_info(host, type);
-      host_infos.push_back(per_host_info);
-      VisitHost(host, type);
-    }
-  }
-  ReportPerOriginInfo(origin_infos);
-  ReportPerHostInfo(host_infos);
-}
-
-void QuotaInternalsProxy::DidDumpQuotaTable(const QuotaTableEntries& entries) {
-  std::vector<PerHostStorageInfo> host_info;
-  host_info.reserve(entries.size());
-
-  for (const auto& entry : entries) {
-    PerHostStorageInfo info(entry.host, entry.type);
-    info.set_quota(entry.quota);
-    host_info.push_back(info);
-  }
-
-  ReportPerHostInfo(host_info);
-}
-
-void QuotaInternalsProxy::DidDumpBucketTable(
-    const BucketTableEntries& entries) {
-  std::vector<PerOriginStorageInfo> origin_info;
-  origin_info.reserve(entries.size());
-
-  for (const auto& entry : entries) {
-    PerOriginStorageInfo info(entry.storage_key.origin().GetURL(), entry.type);
-    info.set_used_count(entry.use_count);
-    info.set_last_access_time(entry.last_accessed);
-    info.set_last_modified_time(entry.last_modified);
-
-    origin_info.push_back(info);
-  }
-
-  ReportPerOriginInfo(origin_info);
-}
-
-void QuotaInternalsProxy::DidGetHostUsage(
-    const std::string& host,
-    StorageType type,
-    int64_t usage,
-    blink::mojom::UsageBreakdownPtr usage_breakdown) {
-  DCHECK(type == StorageType::kTemporary || type == StorageType::kPersistent ||
-         type == StorageType::kSyncable);
-
-  PerHostStorageInfo info(host, type);
-  info.set_usage(usage);
-
-  report_pending_.push_back(info);
-  hosts_pending_.erase(make_pair(host, type));
-  if (report_pending_.size() >= 10 || hosts_pending_.empty()) {
-    ReportPerHostInfo(report_pending_);
-    report_pending_.clear();
-  }
-
-  if (!hosts_pending_.empty())
-    GetHostUsage(hosts_pending_.begin()->first,
-                 hosts_pending_.begin()->second);
-}
-
-void QuotaInternalsProxy::DidGetStatistics(
-    const base::flat_map<std::string, std::string>& stats) {
-  std::map<std::string, std::string> stats_map;
-  for (const auto& stat : stats) {
-    stats_map[stat.first] = stat.second;
-  }
-  ReportStatistics(stats_map);
-}
-
-void QuotaInternalsProxy::VisitHost(const std::string& host, StorageType type) {
-  if (hosts_visited_.insert(std::make_pair(host, type)).second) {
-    hosts_pending_.insert(std::make_pair(host, type));
-    if (hosts_pending_.size() == 1) {
-      GetHostUsage(host, type);
-    }
-  }
-}
-
-void QuotaInternalsProxy::GetHostUsage(const std::string& host,
-                                       StorageType type) {
-  DCHECK(quota_manager_.get());
-  quota_manager_->GetHostUsageWithBreakdown(
-      host, type,
-      base::BindOnce(&QuotaInternalsProxy::DidGetHostUsage,
-                     weak_factory_.GetWeakPtr(), host, type));
-}
-
-}  // namespace quota_internals
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h b/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h
deleted file mode 100644
index 81cf880..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_PROXY_H_
-#define CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_PROXY_H_
-
-#include <stdint.h>
-
-#include <map>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/memory/raw_ptr.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/task/sequenced_task_runner_helpers.h"
-#include "content/public/browser/browser_thread.h"
-#include "storage/browser/quota/quota_manager.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom-forward.h"
-
-namespace quota_internals {
-
-class QuotaInternalsHandler;
-class GlobalStorageInfo;
-class PerHostStorageInfo;
-class PerOriginStorageInfo;
-typedef std::map<std::string, std::string> Statistics;
-
-// This class is the bridge between QuotaInternalsHandler and QuotaManager.
-// Each QuotaInternalsHandler instances creates and owns a instance of this
-// class.
-class QuotaInternalsProxy
-    : public base::RefCountedThreadSafe<
-          QuotaInternalsProxy,
-          content::BrowserThread::DeleteOnIOThread> {
- public:
-  explicit QuotaInternalsProxy(QuotaInternalsHandler* handler);
-
-  QuotaInternalsProxy(const QuotaInternalsProxy&) = delete;
-  QuotaInternalsProxy& operator=(const QuotaInternalsProxy&) = delete;
-
-  void RequestInfo(scoped_refptr<storage::QuotaManager> quota_manager);
-  void TriggerStoragePressure(
-      url::Origin origin,
-      scoped_refptr<storage::QuotaManager> quota_manager);
-
- private:
-  friend class base::DeleteHelper<QuotaInternalsProxy>;
-  friend struct content::BrowserThread::DeleteOnThread<
-      content::BrowserThread::IO>;
-  friend class QuotaInternalsHandler;
-
-  using QuotaTableEntries = storage::QuotaManager::QuotaTableEntries;
-  using BucketTableEntries = storage::QuotaManager::BucketTableEntries;
-
-  virtual ~QuotaInternalsProxy();
-
-  void ReportAvailableSpace(int64_t available_space);
-  void ReportGlobalInfo(const GlobalStorageInfo& data);
-  void ReportPerHostInfo(const std::vector<PerHostStorageInfo>& hosts);
-  void ReportPerOriginInfo(const std::vector<PerOriginStorageInfo>& origins);
-  void ReportStatistics(const Statistics& stats);
-
-  // Called on IO Thread by QuotaManager as callback.
-  void DidGetSettings(const storage::QuotaSettings& settings);
-  void DidGetCapacity(int64_t total_space, int64_t available_space);
-  void DidGetGlobalUsage(blink::mojom::StorageType type,
-                         int64_t usage,
-                         int64_t unlimited_usage);
-  void DidGetStorageKeys(blink::mojom::StorageType type,
-                         const std::set<blink::StorageKey>& storage_keys);
-  void DidDumpQuotaTable(const QuotaTableEntries& entries);
-  void DidDumpBucketTable(const BucketTableEntries& entries);
-  void DidGetHostUsage(const std::string& host,
-                       blink::mojom::StorageType type,
-                       int64_t usage,
-                       blink::mojom::UsageBreakdownPtr usage_breakdown);
-  void DidGetStatistics(
-      const base::flat_map<std::string, std::string>& statistics);
-
-  // Helper. Called on IO Thread.
-  void VisitHost(const std::string& host, blink::mojom::StorageType type);
-  void GetHostUsage(const std::string& host, blink::mojom::StorageType type);
-
-  // Used on UI Thread.
-  raw_ptr<QuotaInternalsHandler> handler_;
-
-  // Used on IO Thread.
-  scoped_refptr<storage::QuotaManager> quota_manager_;
-  std::set<std::pair<std::string, blink::mojom::StorageType>> hosts_visited_,
-      hosts_pending_;
-  std::vector<PerHostStorageInfo> report_pending_;
-  base::WeakPtrFactory<QuotaInternalsProxy> weak_factory_{this};
-};
-}  // namespace quota_internals
-
-#endif  // CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_PROXY_H_
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_types.cc b/chrome/browser/ui/webui/quota_internals/quota_internals_types.cc
deleted file mode 100644
index 958719d4..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_types.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2011 The Chromium 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/ui/webui/quota_internals/quota_internals_types.h"
-
-#include <utility>
-
-#include "base/check.h"
-#include "base/values.h"
-#include "net/base/url_util.h"
-
-namespace {
-
-std::string StorageTypeToString(blink::mojom::StorageType type) {
-  switch (type) {
-    case blink::mojom::StorageType::kTemporary:
-      return "temporary";
-    case blink::mojom::StorageType::kPersistent:
-      return "persistent";
-    case blink::mojom::StorageType::kSyncable:
-      return "syncable";
-    case blink::mojom::StorageType::kQuotaNotManaged:
-      return "quota not managed";
-    case blink::mojom::StorageType::kUnknown:
-      return "unknown";
-  }
-  return "unknown";
-}
-
-}  // anonymous namespace
-
-namespace quota_internals {
-
-GlobalStorageInfo::GlobalStorageInfo(blink::mojom::StorageType type)
-    : type_(type), usage_(-1), unlimited_usage_(-1), quota_(-1) {}
-
-GlobalStorageInfo::~GlobalStorageInfo() {}
-
-base::Value GlobalStorageInfo::NewValue() const {
-  // TODO(tzik): Add CreateLongIntegerValue to base/values.h and remove
-  // all static_cast<double> in this file.
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetStringKey("type", StorageTypeToString(type_));
-  if (usage_ >= 0)
-    dict.SetDoubleKey("usage", static_cast<double>(usage_));
-  if (unlimited_usage_ >= 0)
-    dict.SetDoubleKey("unlimitedUsage", static_cast<double>(unlimited_usage_));
-  if (quota_ >= 0)
-    dict.SetDoubleKey("quota", static_cast<double>(quota_));
-  return dict;
-}
-
-PerHostStorageInfo::PerHostStorageInfo(const std::string& host,
-                                       blink::mojom::StorageType type)
-    : host_(host), type_(type), usage_(-1), quota_(-1) {}
-
-PerHostStorageInfo::~PerHostStorageInfo() {}
-
-base::Value PerHostStorageInfo::NewValue() const {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  DCHECK(!host_.empty());
-  dict.SetStringKey("host", host_);
-  dict.SetStringKey("type", StorageTypeToString(type_));
-  if (usage_ >= 0)
-    dict.SetDoubleKey("usage", static_cast<double>(usage_));
-  if (quota_ >= 0)
-    dict.SetDoubleKey("quota", static_cast<double>(quota_));
-  return dict;
-}
-
-PerOriginStorageInfo::PerOriginStorageInfo(const GURL& origin,
-                                           blink::mojom::StorageType type)
-    : origin_(origin),
-      type_(type),
-      host_(origin.host()),
-      used_count_(-1) {}
-
-PerOriginStorageInfo::PerOriginStorageInfo(const PerOriginStorageInfo& other) =
-    default;
-
-PerOriginStorageInfo::~PerOriginStorageInfo() {}
-
-base::Value PerOriginStorageInfo::NewValue() const {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  DCHECK(!origin_.is_empty());
-  DCHECK(!host_.empty());
-  dict.SetStringKey("origin", origin_.spec());
-  dict.SetStringKey("type", StorageTypeToString(type_));
-  dict.SetStringKey("host", host_);
-  if (used_count_ >= 0)
-    dict.SetIntKey("usedCount", used_count_);
-  if (!last_access_time_.is_null())
-    dict.SetDoubleKey("lastAccessTime", last_access_time_.ToDoubleT() * 1000.0);
-  if (!last_modified_time_.is_null()) {
-    dict.SetDoubleKey("lastModifiedTime",
-                      last_modified_time_.ToDoubleT() * 1000.0);
-  }
-  return dict;
-}
-
-}  // namespace quota_internals
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_types.h b/chrome/browser/ui/webui/quota_internals/quota_internals_types.h
deleted file mode 100644
index 873bc86..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_types.h
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2011 The Chromium 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_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_TYPES_H_
-#define CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_TYPES_H_
-
-#include <stdint.h>
-
-#include <map>
-#include <memory>
-#include <string>
-
-#include "base/time/time.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
-#include "url/gurl.h"
-
-namespace base {
-class Value;
-}
-
-namespace quota_internals {
-
-// Represents global usage and quota information for specific type of storage.
-class GlobalStorageInfo {
- public:
-  explicit GlobalStorageInfo(blink::mojom::StorageType type);
-  ~GlobalStorageInfo();
-
-  void set_usage(int64_t usage) { usage_ = usage; }
-
-  void set_unlimited_usage(int64_t unlimited_usage) {
-    unlimited_usage_ = unlimited_usage;
-  }
-
-  void set_quota(int64_t quota) { quota_ = quota; }
-
-  // Create new Value for passing to WebUI page.
-  base::Value NewValue() const;
-
- private:
-  blink::mojom::StorageType type_;
-
-  int64_t usage_;
-  int64_t unlimited_usage_;
-  int64_t quota_;
-};
-
-// Represents per host usage and quota information for the storage.
-class PerHostStorageInfo {
- public:
-  PerHostStorageInfo(const std::string& host, blink::mojom::StorageType type);
-  ~PerHostStorageInfo();
-
-  void set_usage(int64_t usage) { usage_ = usage; }
-
-  void set_quota(int64_t quota) { quota_ = quota; }
-
-  // Create new Value for passing to WebUI page.
-  base::Value NewValue() const;
-
- private:
-  std::string host_;
-  blink::mojom::StorageType type_;
-
-  int64_t usage_;
-  int64_t quota_;
-};
-
-// Represents per origin usage and access time information.
-class PerOriginStorageInfo {
- public:
-  PerOriginStorageInfo(const GURL& origin, blink::mojom::StorageType type);
-  PerOriginStorageInfo(const PerOriginStorageInfo& other);
-  ~PerOriginStorageInfo();
-
-  void set_used_count(int used_count) {
-    used_count_ = used_count;
-  }
-
-  void set_last_access_time(base::Time last_access_time) {
-    last_access_time_ = last_access_time;
-  }
-
-  void set_last_modified_time(base::Time last_modified_time) {
-    last_modified_time_ = last_modified_time;
-  }
-
-  // Create new Value for passing to WebUI page.
-  base::Value NewValue() const;
-
- private:
-  GURL origin_;
-  blink::mojom::StorageType type_;
-  std::string host_;
-
-  int used_count_;
-  base::Time last_access_time_;
-  base::Time last_modified_time_;
-};
-}  // namespace quota_internals
-
-#endif  // CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_TYPES_H_
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_ui.cc b/chrome/browser/ui/webui/quota_internals/quota_internals_ui.cc
deleted file mode 100644
index 15f04e9..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_ui.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_ui.h"
-
-#include <memory>
-#include <string>
-
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/quota_internals/quota_internals_handler.h"
-#include "chrome/browser/ui/webui/webui_util.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/grit/quota_internals_resources.h"
-#include "chrome/grit/quota_internals_resources_map.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_ui.h"
-#include "content/public/browser/web_ui_data_source.h"
-#include "services/network/public/mojom/content_security_policy.mojom.h"
-
-using content::WebContents;
-
-namespace {
-
-content::WebUIDataSource* CreateQuotaInternalsHTMLSource() {
-  content::WebUIDataSource* source =
-      content::WebUIDataSource::Create(chrome::kChromeUIQuotaInternalsHost);
-  source->UseStringsJs();
-  source->AddResourcePaths(
-      base::make_span(kQuotaInternalsResources, kQuotaInternalsResourcesSize));
-  source->AddResourcePath("", IDR_QUOTA_INTERNALS_MAIN_HTML);
-  source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::TrustedTypes,
-      "trusted-types static-types;");
-  return source;
-}
-
-}  // namespace
-
-QuotaInternalsUI::QuotaInternalsUI(content::WebUI* web_ui)
-    : WebUIController(web_ui) {
-  web_ui->AddMessageHandler(
-      std::make_unique<quota_internals::QuotaInternalsHandler>());
-  Profile* profile = Profile::FromWebUI(web_ui);
-  content::WebUIDataSource::Add(profile, CreateQuotaInternalsHTMLSource());
-}
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_ui.h b/chrome/browser/ui/webui/quota_internals/quota_internals_ui.h
deleted file mode 100644
index 989e0f7..0000000
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_ui.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2011 The Chromium 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_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_UI_H_
-#define CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_UI_H_
-
-#include "content/public/browser/web_ui_controller.h"
-
-class QuotaInternalsUI : public content::WebUIController {
- public:
-  explicit QuotaInternalsUI(content::WebUI* web_ui);
-
-  QuotaInternalsUI(const QuotaInternalsUI&) = delete;
-  QuotaInternalsUI& operator=(const QuotaInternalsUI&) = delete;
-
-  ~QuotaInternalsUI() override {}
-};
-
-#endif  // CHROME_BROWSER_UI_WEBUI_QUOTA_INTERNALS_QUOTA_INTERNALS_UI_H_
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
index 4d02856..8b07d26 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
@@ -121,6 +121,17 @@
   return dict;
 }
 
+::account_manager::Account ValueToGaiaAccount(const base::Value& value) {
+  DCHECK(value.is_dict());
+  const std::string* id = value.FindStringKey(kAccountKeyId);
+  DCHECK(id);
+  const std::string* email = value.FindStringKey(kAccountKeyEmail);
+  DCHECK(email);
+  return ::account_manager::Account{
+      ::account_manager::AccountKey{*id, account_manager::AccountType::kGaia},
+      *email};
+}
+
 class EduCoexistenceChildSigninHelper : public SigninHelper {
  public:
   EduCoexistenceChildSigninHelper(
@@ -233,6 +244,11 @@
       base::BindRepeating(
           &InlineLoginHandlerChromeOS::GetAccountsNotAvailableInArc,
           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "makeAvailableInArc",
+      base::BindRepeating(
+          &InlineLoginHandlerChromeOS::MakeAvailableInArcAndCloseDialog,
+          base::Unretained(this)));
   web_ui()->RegisterDeprecatedMessageCallback(
       "skipWelcomePage",
       base::BindRepeating(&InlineLoginHandlerChromeOS::HandleSkipWelcomePage,
@@ -424,6 +440,18 @@
   ResolveJavascriptCallback(base::Value(callback_id), std::move(result));
 }
 
+void InlineLoginHandlerChromeOS::MakeAvailableInArcAndCloseDialog(
+    base::Value::ConstListView args) {
+  CHECK_EQ(1u, args.size());
+  const base::Value& dictionary = args[0];
+  CHECK(dictionary.is_dict());
+  ash::AccountAppsAvailabilityFactory::GetForProfile(
+      Profile::FromWebUI(web_ui()))
+      ->SetIsAccountAvailableInArc(ValueToGaiaAccount(dictionary),
+                                   /*is_available=*/true);
+  close_dialog_closure_.Run();
+}
+
 void InlineLoginHandlerChromeOS::HandleSkipWelcomePage(
     const base::ListValue* args) {
   const auto& list = args->GetList();
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.h b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.h
index 8a7794659..6f16f43 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.h
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.h
@@ -58,6 +58,7 @@
       const std::string& callback_id,
       const std::vector<::account_manager::Account>& accounts,
       const base::flat_set<account_manager::Account>& arc_accounts);
+  void MakeAvailableInArcAndCloseDialog(base::Value::ConstListView args);
   void HandleSkipWelcomePage(const base::ListValue* args);
 
   base::RepeatingClosure close_dialog_closure_;
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos_browsertest.cc b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos_browsertest.cc
index 47467e4a..c686cca 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos_browsertest.cc
@@ -63,6 +63,7 @@
 constexpr char kSecondaryAccountOAuthCode[] = "fake_oauth_code";
 constexpr char kSecondaryAccountRefreshToken[] = "fake_refresh_token";
 constexpr char kCompleteLoginMessage[] = "completeLogin";
+constexpr char kMakeAvailableInArcMessage[] = "makeAvailableInArc";
 constexpr char kGetAccountsNotAvailableInArcMessage[] =
     "getAccountsNotAvailableInArc";
 constexpr char kHandleFunctionName[] = "handleFunctionName";
@@ -413,13 +414,37 @@
 
   bool ValuesListContainAccount(const base::span<const base::Value> values,
                                 const std::string& email) {
+    return ValuesListGetAccount(values, email).has_value();
+  }
+
+  absl::optional<base::Value> ValuesListGetAccount(
+      const base::span<const base::Value> values,
+      const std::string& email) {
     for (const base::Value& value : values) {
       const std::string* email_val = value.FindStringKey("email");
       EXPECT_TRUE(email_val != nullptr);
       if (*email_val == email)
-        return true;
+        return value.Clone();
     }
-    return false;
+    return absl::nullopt;
+  }
+
+  const base::span<const base::Value> CallGetAccountsNotAvailableInArc() {
+    // Call "getAccountsNotAvailableInArc".
+    base::Value args(base::Value::Type::LIST);
+    args.Append(kHandleFunctionName);
+    web_ui()->HandleReceivedMessage(kGetAccountsNotAvailableInArcMessage,
+                                    &base::Value::AsListValue(args));
+    base::RunLoop().RunUntilIdle();
+
+    const content::TestWebUI::CallData& call_data =
+        *web_ui()->call_data().back();
+    EXPECT_EQ("cr.webUIResponse", call_data.function_name());
+    EXPECT_EQ(kHandleFunctionName, call_data.arg1()->GetString());
+    EXPECT_TRUE(call_data.arg2()->GetBool());
+
+    // Get results from JS callback.
+    return call_data.arg3()->GetList();
   }
 
  private:
@@ -506,19 +531,8 @@
   AddAccount(kSecondaryAccount3Email, /*is_available_in_arc=*/false);
 
   // Call "getAccountsNotAvailableInArc".
-  base::Value args(base::Value::Type::LIST);
-  args.Append(kHandleFunctionName);
-  web_ui()->HandleReceivedMessage(kGetAccountsNotAvailableInArcMessage,
-                                  &base::Value::AsListValue(args));
-  base::RunLoop().RunUntilIdle();
-
-  const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
-  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
-  EXPECT_EQ(kHandleFunctionName, call_data.arg1()->GetString());
-  ASSERT_TRUE(call_data.arg2()->GetBool());
-
-  // Get results from JS callback.
-  const base::span<const base::Value> result = call_data.arg3()->GetList();
+  const base::span<const base::Value> result =
+      CallGetAccountsNotAvailableInArc();
   // Two accounts are not available in ARC.
   EXPECT_EQ(2, result.size());
   EXPECT_FALSE(ValuesListContainAccount(result, kSecondaryAccount1Email));
@@ -526,6 +540,32 @@
   EXPECT_TRUE(ValuesListContainAccount(result, kSecondaryAccount3Email));
 }
 
+IN_PROC_BROWSER_TEST_P(InlineLoginHandlerChromeOSTestWithArcRestrictions,
+                       MakeAvailableInArc) {
+  AddAccount(kSecondaryAccount1Email, /*is_available_in_arc=*/true);
+  AddAccount(kSecondaryAccount2Email, /*is_available_in_arc=*/false);
+
+  // Call "getAccountsNotAvailableInArc".
+  const base::span<const base::Value> result =
+      CallGetAccountsNotAvailableInArc();
+  // One account is not available in ARC.
+  EXPECT_EQ(1, result.size());
+  EXPECT_FALSE(ValuesListContainAccount(result, kSecondaryAccount1Email));
+  EXPECT_TRUE(ValuesListContainAccount(result, kSecondaryAccount2Email));
+
+  // Call "makeAvailableInArc".
+  base::Value args_1(base::Value::Type::LIST);
+  args_1.Append(ValuesListGetAccount(result, kSecondaryAccount2Email).value());
+  web_ui()->HandleReceivedMessage(kMakeAvailableInArcMessage,
+                                  &base::Value::AsListValue(args_1));
+
+  // Call "getAccountsNotAvailableInArc".
+  const base::span<const base::Value> result_1 =
+      CallGetAccountsNotAvailableInArc();
+  // Zero accounts are not available in ARC.
+  EXPECT_EQ(0, result_1.size());
+}
+
 INSTANTIATE_TEST_SUITE_P(InlineLoginHandlerChromeOSTestWithArcRestrictionsSuite,
                          InlineLoginHandlerChromeOSTestWithArcRestrictions,
                          ::testing::Values(GetGaiaDeviceAccountInfo(),
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
index f8f6a349..a672e327 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_page_handler.cc
@@ -598,11 +598,11 @@
           /* 16% opacity */ 0.16 * 255));
 
   std::string throbber_color = color_utils::SkColorToRgbaString(
-      embedder_->GetColor(ThemeProperties::COLOR_TAB_THROBBER_SPINNING));
+      embedder_->GetColorProviderColor(ui::kColorThrobber));
   colors["--tabstrip-tab-loading-spinning-color"] = throbber_color;
   colors["--tabstrip-tab-waiting-spinning-color"] =
       color_utils::SkColorToRgbaString(
-          embedder_->GetColor(ThemeProperties::COLOR_TAB_THROBBER_WAITING));
+          embedder_->GetColorProviderColor(ui::kColorThrobberPreconnect));
   colors["--tabstrip-indicator-recording-color"] =
       color_utils::SkColorToRgbaString(
           embedder_->GetColorProviderColor(ui::kColorAlertHighSeverity));
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index e8ce158..44e469c19 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1643284754-95e64ca29eb53e4d882fc2eecd37abeaef1d7e33.profdata
+chrome-mac-arm-main-1643306390-7032204a04a8b859e8609b7773ac3e7e043aeac7.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index f4a4bee..4951b7b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1643284754-e6072bf75b1d01e02013b9c0a038e83f0e166a59.profdata
+chrome-mac-main-1643306390-b51115e6a28640651dc4c4d7bafac76a4a6bac80.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 134a42b..7afc549 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1643295498-1e46a15413da3e019634ff4ee57ba12b04a94734.profdata
+chrome-win32-main-1643306390-4fff76475fcca879feb7aeb7adcea44a12b5a086.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index a672d13..d38597b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1643295498-2123a7627edf68fdccd462dd15b336c8ca0c40fd.profdata
+chrome-win64-main-1643306390-cae13a233894eb61c2f7c603004b4a48a0699857.profdata
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index be0dfbe..41bf82a3 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -271,6 +271,12 @@
             "type": "string",
             "description": "The text to be displayed in the bubble UI. If `text` is undefined, the bubble will clear its current text.",
             "optional": true
+          },
+          "hints": {
+            "type": "array",
+            "items": { "type": "string" },
+            "description": "Array of hints to show in the UI.",
+            "optional": true
           }
         }
       }
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 926ea21..92e80ad 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -157,9 +157,7 @@
     "chrome://privacy-sandbox-dialog";
 const char kChromeUIQuitHost[] = "quit";
 const char kChromeUIQuitURL[] = "chrome://quit/";
-// TODO(crbug.com/1202165): Remove when new quota-internals page is done.
 const char kChromeUIQuotaInternalsHost[] = "quota-internals";
-const char kChromeUIQuotaInternals2Host[] = "quota-internals-2";
 const char kChromeUIResetPasswordHost[] = "reset-password";
 const char kChromeUIResetPasswordURL[] = "chrome://reset-password/";
 const char kChromeUIRestartHost[] = "restart";
@@ -607,9 +605,7 @@
     kChromeUIPolicyHost,
     kChromeUIPredictorsHost,
     kChromeUIPrefsInternalsHost,
-    // TODO(crbug.com/1202165): Remove when new quota-internals page is done.
     kChromeUIQuotaInternalsHost,
-    kChromeUIQuotaInternals2Host,
     kChromeUISignInInternalsHost,
     kChromeUISiteEngagementHost,
     kChromeUINTPTilesInternalsHost,
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 7e6a109..e928ce3 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -156,9 +156,6 @@
 extern const char kChromeUIPrivacySandboxDialogURL[];
 extern const char kChromeUIQuitHost[];
 extern const char kChromeUIQuitURL[];
-// TODO(crbug.com/1202165): Remove when new quota-internals page is done.
-extern const char kChromeUIQuotaInternalsHost[];
-extern const char kChromeUIQuotaInternals2Host[];
 extern const char kChromeUIResetPasswordHost[];
 extern const char kChromeUIResetPasswordURL[];
 extern const char kChromeUIRestartHost[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 50914408..5ecbe6d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3545,6 +3545,7 @@
         "../browser/ui/browser_navigator_browsertest_chromeos.cc",
         "../browser/ui/extensions/application_launch_browsertest.cc",
         "../browser/ui/settings_window_manager_browsertest_chromeos.cc",
+        "../browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
@@ -4085,6 +4086,9 @@
     ]
 
     sources = [
+      "../browser/chromeos/policy/dlp/dlp_content_manager_lacros_browsertest.cc",
+      "../browser/chromeos/policy/dlp/dlp_content_manager_test_helper.cc",
+      "../browser/chromeos/policy/dlp/dlp_content_manager_test_helper.h",
       "../browser/lacros/browser_service_lacros_browsertest.cc",
       "../browser/lacros/browser_test_util.cc",
       "../browser/lacros/browser_test_util.h",
@@ -8622,6 +8626,7 @@
         "../browser/ui/views/user_education/browser_feature_promo_controller_interactive_uitest.cc",
         "../browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc",
         "../browser/ui/views/user_education/feature_promo_snooze_interactive_uitest.cc",
+        "../browser/ui/views/user_education/help_bubble_factory_views_interactive_uitest.cc",
         "../browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc",
         "../browser/ui/views/user_education/interaction_sequence_interactive_uitest.cc",
       ]
@@ -8802,12 +8807,10 @@
     }
 
     if (enable_extensions) {
-      # TODO(rockot) bug 505926: The chrome_extensions_interactive_uitests
-      # target should be deleted and this line removed. See the
-      # chrome_extensions_interactive_uitests target for more.
-      deps += [ "//extensions:chrome_extensions_interactive_uitests" ]
-
-      sources += [ "../browser/extensions/api/extension_action/extension_action_api_interactive_uitest.cc" ]
+      sources += [
+        "../browser/extensions/api/extension_action/extension_action_api_interactive_uitest.cc",
+        "../browser/guest_view/mime_handler_view/chrome_mime_handler_view_interactive_uitest.cc",
+      ]
 
       if (include_js_tests) {
         sources += [
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
index 805f2c9..c4b825c 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdown;
 import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderView;
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
+import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.content_public.browser.test.util.KeyUtils;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -56,6 +57,7 @@
     private final @NonNull AutocompleteCoordinator mAutocomplete;
     private final @NonNull UrlBar mUrlBar;
     private final @NonNull Instrumentation mInstrumentation;
+    private final @Nullable ToolbarLayout mToolbar;
 
     /**
      * Class describing individual suggestion, delivering access to broad range of information.
@@ -91,8 +93,10 @@
         mActivity = activity;
         if (activity instanceof SearchActivity) {
             mLocationBar = mActivity.findViewById(R.id.search_location_bar);
+            mToolbar = null;
         } else {
             mLocationBar = mActivity.findViewById(R.id.location_bar);
+            mToolbar = mActivity.findViewById(R.id.toolbar);
         }
         mAutocomplete = mLocationBar.getAutocompleteCoordinator();
         mUrlBar = mActivity.findViewById(R.id.url_bar);
@@ -107,12 +111,24 @@
     }
 
     /**
+     * Waits for all the animations to complete.
+     * Allows any preceding operation to kick off an animation.
+     */
+    public void waitAnimationsComplete() {
+        // Note: SearchActivity has no toolbar and no animations, but we still need to
+        // give keyboard a bit of time to pop up (requested with delay).
+        do {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        } while (mToolbar != null && mToolbar.isAnimationRunningForTesting());
+    }
+
+    /**
      * Check that the Omnibox reaches the expected focus state.
      *
      * @param active Whether the Omnibox is expected to have focus or not.
      */
     public void checkFocus(boolean active) {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        waitAnimationsComplete();
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(
                     "unexpected Omnibox focus state", mUrlBar.hasFocus(), Matchers.is(active));
@@ -135,6 +151,7 @@
      * Request the Omnibox focus and wait for soft keyboard to show.
      */
     public void requestFocus() {
+        waitAnimationsComplete();
         // During early startup (before completion of its first onDraw), the UrlBar
         // is not focusable. Tests have to wait for that to happen before trying to focus it.
         CriteriaHelper.pollUiThread(() -> {
@@ -151,6 +168,7 @@
      * Expects the Omnibox to be focused before the call.
      */
     public void clearFocus() {
+        waitAnimationsComplete();
         sendKey(KeyEvent.KEYCODE_BACK);
         checkFocus(false);
     }
diff --git a/chrome/test/base/devtools_listener.cc b/chrome/test/base/devtools_listener.cc
index 1692612..b5c5c44 100644
--- a/chrome/test/base/devtools_listener.cc
+++ b/chrome/test/base/devtools_listener.cc
@@ -153,17 +153,17 @@
 
   auto entries = std::make_unique<base::ListValue>();
   for (size_t i = 0; i != coverage_entries->GetList().size(); ++i) {
-    base::DictionaryValue* entry = nullptr;
-    CHECK(coverage_entries->GetDictionary(i, &entry));
+    base::Value& entry = coverage_entries->GetList()[i];
+    CHECK(entry.is_dict());
 
-    std::string* script_id = entry->FindStringKey("scriptId");
+    std::string* script_id = entry.FindStringKey("scriptId");
     CHECK(script_id);
     const auto it = script_id_map_.find(*script_id);
     if (it == script_id_map_.end())
       continue;
 
-    CHECK(entry->SetString("hash", it->second));
-    entries->Append(entry->CreateDeepCopy());
+    CHECK(entry.SetStringKey("hash", it->second));
+    entries->Append(entry.CreateDeepCopy());
   }
 
   std::string url = host->GetURL().spec();
diff --git a/chrome/test/data/extensions/api_test/accessibility_private/background.js b/chrome/test/data/extensions/api_test/accessibility_private/background.js
index 1c412f48..08ae1b0 100644
--- a/chrome/test/data/extensions/api_test/accessibility_private/background.js
+++ b/chrome/test/data/extensions/api_test/accessibility_private/background.js
@@ -135,6 +135,22 @@
     chrome.test.notifyPass();
   },
 
+  function testUpdateDictationBubbleWithHints() {
+    const update = chrome.accessibilityPrivate.updateDictationBubble;
+    const IconType = chrome.accessibilityPrivate.DictationBubbleIconType;
+    update({
+      visible: true,
+      icon: IconType.STANDBY,
+      hints: ['One', 'Two', 'Three']
+    });
+    chrome.test.sendMessage('Some hints', (proceed) => {
+      update({visible: true, icon: IconType.STANDBY});
+      chrome.test.sendMessage('No hints');
+      chrome.test.succeed();
+    });
+
+    chrome.test.notifyPass();
+  },
 ];
 
 chrome.test.getConfig(function(config) {
diff --git a/chrome/test/data/webui/inline_login/arc_account_picker_page_test.js b/chrome/test/data/webui/inline_login/arc_account_picker_page_test.js
index 09b3a40..21476b8 100644
--- a/chrome/test/data/webui/inline_login/arc_account_picker_page_test.js
+++ b/chrome/test/data/webui/inline_login/arc_account_picker_page_test.js
@@ -9,7 +9,7 @@
 import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {assertEquals} from '../chai_assert.js';
+import {assertDeepEquals, assertEquals} from '../chai_assert.js';
 
 import {fakeAuthExtensionData, fakeAuthExtensionDataWithEmail, TestAuthenticator, TestInlineLoginBrowserProxy} from './inline_login_test_util.js';
 
@@ -23,6 +23,7 @@
   ArcPickerHiddenForReauth: 'ArcPickerHiddenForReauth',
   ArcPickerHiddenNoAccounts: 'ArcPickerHiddenNoAccounts',
   AddAccount: 'AddAccount',
+  MakeAvailableInArc: 'MakeAvailableInArc',
 };
 
 /** @return {!Array<Account>} */
@@ -149,4 +150,30 @@
         inlineLoginComponent.View.welcome, getActiveViewId(),
         'Welcome screen should be active after Add account button click');
   });
+
+  test(
+      assert(arc_account_picker_page_test.TestNames.MakeAvailableInArc),
+      async () => {
+        testSetup(
+            {isAvailableInArc: true, showArcAvailabilityPicker: true},
+            getFakeAccountsNotAvailableInArcList());
+        // Send auth extension data without email -> it's account addition flow.
+        webUIListenerCallback('load-auth-extension', fakeAuthExtensionData);
+        // Wait for getAccountsNotAvailableInArc call which will return > 0
+        // accounts.
+        await testBrowserProxy.whenCalled('getAccountsNotAvailableInArc');
+        flush();
+        assertEquals(
+            inlineLoginComponent.View.arcAccountPicker, getActiveViewId(),
+            'ARC account picker screen should be active');
+
+        const expectedAccount = getFakeAccountsNotAvailableInArcList()[0];
+        arcAccountPickerComponent.shadowRoot
+            .querySelectorAll('.account-item')[0]
+            .click();
+        return testBrowserProxy.whenCalled('makeAvailableInArc')
+            .then(function(account) {
+              assertDeepEquals(expectedAccount, account);
+            });
+      });
 });
diff --git a/chrome/test/data/webui/inline_login/inline_login_browsertest.js b/chrome/test/data/webui/inline_login/inline_login_browsertest.js
index d5eb9f5..b69129f 100644
--- a/chrome/test/data/webui/inline_login/inline_login_browsertest.js
+++ b/chrome/test/data/webui/inline_login/inline_login_browsertest.js
@@ -229,4 +229,10 @@
 TEST_F('InlineLoginArcAccountPickerBrowserTest', 'AddAccount', function() {
   this.runMochaTest(arc_account_picker_page_test.TestNames.AddAccount);
 });
+
+TEST_F(
+    'InlineLoginArcAccountPickerBrowserTest', 'MakeAvailableInArc', function() {
+      this.runMochaTest(
+          arc_account_picker_page_test.TestNames.MakeAvailableInArc);
+    });
 GEN('#endif');
diff --git a/chrome/test/data/webui/inline_login/inline_login_test_util.js b/chrome/test/data/webui/inline_login/inline_login_test_util.js
index ccb5e22..6a666bfa 100644
--- a/chrome/test/data/webui/inline_login/inline_login_test_util.js
+++ b/chrome/test/data/webui/inline_login/inline_login_test_util.js
@@ -89,6 +89,7 @@
       // <if expr="chromeos">
       'skipWelcomePage',
       'getAccountsNotAvailableInArc',
+      'makeAvailableInArc',
       'getDialogArguments',
       // </if>
     ]);
@@ -178,6 +179,11 @@
   }
 
   /** @override */
+  makeAvailableInArc(account) {
+    this.methodCalled('makeAvailableInArc', account);
+  }
+
+  /** @override */
   getDialogArguments() {
     return JSON.stringify(this.dialogArguments_);
   }
diff --git a/chrome/updater/mac/privileged_helper/server.h b/chrome/updater/mac/privileged_helper/server.h
index 2d3d4fc3..9cf232cf 100644
--- a/chrome/updater/mac/privileged_helper/server.h
+++ b/chrome/updater/mac/privileged_helper/server.h
@@ -8,6 +8,7 @@
 #include "base/mac/scoped_nsobject.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
+#include "base/time/time.h"
 #include "chrome/updater/app/app.h"
 #include "chrome/updater/mac/privileged_helper/service.h"
 
@@ -16,6 +17,8 @@
 class PrivilegedHelperServer : public App {
  public:
   PrivilegedHelperServer();
+  void TaskStarted();
+  void TaskCompleted();
 
  protected:
   ~PrivilegedHelperServer() override;
@@ -28,9 +31,15 @@
   void FirstTaskRun() override;
   void Uninitialize() override;
 
+  void MarkTaskStarted();
+  void AcknowledgeTaskCompletion();
+  base::TimeDelta ServerKeepAlive();
+
+  scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
   scoped_refptr<PrivilegedHelperService> service_;
   base::scoped_nsobject<NSXPCListener> service_listener_;
   base::scoped_nsobject<PrivilegedHelperServiceXPCDelegate> service_delegate_;
+  int tasks_running_ = 0;
 };
 
 scoped_refptr<App> PrivilegedHelperServerInstance();
diff --git a/chrome/updater/mac/privileged_helper/server.mm b/chrome/updater/mac/privileged_helper/server.mm
index d0dbd7c..61b52214 100644
--- a/chrome/updater/mac/privileged_helper/server.mm
+++ b/chrome/updater/mac/privileged_helper/server.mm
@@ -4,16 +4,25 @@
 
 #include "chrome/updater/mac/privileged_helper/server.h"
 
+#include "base/bind.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/task/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
 #include "chrome/updater/updater_branding.h"
 
 namespace updater {
 
+namespace {
+int kServerKeepAliveSeconds = 10;
+}
+
 PrivilegedHelperServer::PrivilegedHelperServer()
-    : service_(base::MakeRefCounted<PrivilegedHelperService>()) {}
+    : main_task_runner_(base::SequencedTaskRunnerHandle::Get()),
+      service_(base::MakeRefCounted<PrivilegedHelperService>()) {}
 PrivilegedHelperServer::~PrivilegedHelperServer() = default;
 
 void PrivilegedHelperServer::Initialize() {
@@ -38,6 +47,36 @@
   service_listener_.reset();
 }
 
+void PrivilegedHelperServer::TaskStarted() {
+  main_task_runner_->PostTask(
+      FROM_HERE, BindOnce(&PrivilegedHelperServer::MarkTaskStarted, this));
+}
+
+void PrivilegedHelperServer::MarkTaskStarted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ++tasks_running_;
+}
+
+base::TimeDelta PrivilegedHelperServer::ServerKeepAlive() {
+  int seconds = kServerKeepAliveSeconds;
+  return base::Seconds(seconds);
+}
+
+void PrivilegedHelperServer::TaskCompleted() {
+  main_task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&PrivilegedHelperServer::AcknowledgeTaskCompletion, this),
+      ServerKeepAlive());
+}
+
+void PrivilegedHelperServer::AcknowledgeTaskCompletion() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (--tasks_running_ < 1) {
+    main_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&PrivilegedHelperServer::Shutdown, this, 0));
+  }
+}
+
 scoped_refptr<App> PrivilegedHelperServerInstance() {
   return base::MakeRefCounted<PrivilegedHelperServer>();
 }
diff --git a/chrome/updater/mac/privileged_helper/service.mm b/chrome/updater/mac/privileged_helper/service.mm
index 08319f6..030995e 100644
--- a/chrome/updater/mac/privileged_helper/service.mm
+++ b/chrome/updater/mac/privileged_helper/service.mm
@@ -60,8 +60,10 @@
     VLOG(0) << "SetupSystemUpdaterWithUpdaterPath complete. Result: " << rc;
     if (reply)
       reply(rc);
+    _server->TaskCompleted();
   }));
 
+  _server->TaskStarted();
   _callbackRunner->PostTask(
       FROM_HERE,
       base::BindOnce(&updater::PrivilegedHelperService::SetupSystemUpdater,
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
index 3deb001..ffccf93b 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastAudioManager.java
@@ -99,6 +99,10 @@
         mInternal.unregisterAudioPolicyAsync(audioPolicy);
     }
 
+    public void unregisterAudioPolicy(AudioPolicy audioPolicy) {
+        mInternal.unregisterAudioPolicy(audioPolicy);
+    }
+
     @TargetApi(VERSION_CODES.M)
     public AudioDeviceInfo[] getDevices(int flags) {
         return mInternal.getDevices(flags);
diff --git a/chromecast/browser/cast_extension_message_filter.cc b/chromecast/browser/cast_extension_message_filter.cc
index 52002b94..e2388b75 100644
--- a/chromecast/browser/cast_extension_message_filter.cc
+++ b/chromecast/browser/cast_extension_message_filter.cc
@@ -21,9 +21,9 @@
 #include "content/public/browser/render_process_host.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/info_map.h"
+#include "extensions/browser/l10n_file_util.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_set.h"
-#include "extensions/common/file_util.h"
 #include "extensions/common/manifest_handlers/default_locale_handler.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "extensions/common/message_bundle.h"
@@ -95,8 +95,8 @@
   if (default_locale.empty()) {
     // A little optimization: send the answer here to avoid an extra thread hop.
     std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
-        extensions::file_util::LoadNonLocalizedMessageBundleSubstitutionMap(
-            extension_id));
+        extensions::l10n_file_util::
+            LoadNonLocalizedMessageBundleSubstitutionMap(extension_id));
     ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
                                                         *dictionary_map);
     Send(reply_msg);
@@ -138,7 +138,7 @@
     extension_l10n_util::GzippedMessagesPermission gzip_permission,
     IPC::Message* reply_msg) {
   std::unique_ptr<extensions::MessageBundle::SubstitutionMap> dictionary_map(
-      extensions::file_util::LoadMessageBundleSubstitutionMapFromPaths(
+      extensions::l10n_file_util::LoadMessageBundleSubstitutionMapFromPaths(
           extension_paths, main_extension_id, default_locale, gzip_permission));
 
   ExtensionHostMsg_GetMessageBundle::WriteReplyParams(reply_msg,
diff --git a/chromecast/media/audio/cma_audio_output.cc b/chromecast/media/audio/cma_audio_output.cc
index 76d42b0..e9941af 100644
--- a/chromecast/media/audio/cma_audio_output.cc
+++ b/chromecast/media/audio/cma_audio_output.cc
@@ -57,16 +57,20 @@
     SampleFormat sample_format,
     const std::string& device_id,
     const std::string& application_session_id,
+    bool use_hw_av_sync,
+    int audio_track_session_id,
     chromecast::mojom::MultiroomInfoPtr multiroom_info,
     CmaBackendFactory* cma_backend_factory,
     CmaBackend::Decoder::Delegate* delegate)
     : audio_params_(audio_params),
       sample_size_(GetSampleSize(sample_format)),
+      use_hw_av_sync_(use_hw_av_sync),
       delegate_(delegate),
       timestamp_helper_(audio_params_.sample_rate()) {
   DCHECK(delegate_);
   Initialize(sample_format, device_id, application_session_id,
-             std::move(multiroom_info), cma_backend_factory);
+             audio_track_session_id, std::move(multiroom_info),
+             cma_backend_factory);
 }
 
 CmaAudioOutput::~CmaAudioOutput() = default;
@@ -75,6 +79,7 @@
     SampleFormat sample_format,
     const std::string& device_id,
     const std::string& application_session_id,
+    int audio_track_session_id,
     chromecast::mojom::MultiroomInfoPtr multiroom_info,
     CmaBackendFactory* cma_backend_factory) {
   DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
@@ -114,6 +119,8 @@
   audio_config.bytes_per_channel = sample_size_;
   audio_config.channel_number = audio_params_.channels();
   audio_config.samples_per_second = audio_params_.sample_rate();
+  audio_config.use_hw_av_sync = use_hw_av_sync_;
+  audio_config.audio_track_session_id = audio_track_session_id;
   DCHECK(IsValidConfig(audio_config));
   // Need to first set the config of the audio decoder then initialize the cma
   // backend if succeed.
@@ -184,7 +191,11 @@
       0u);
   DCHECK(audio_decoder_);
 
-  decoder_buffer->set_timestamp(timestamp_helper_.GetTimestamp());
+  if (!use_hw_av_sync_) {
+    // Keep the timestamp of the buffer if the stream is on hardware av sync
+    // mode.
+    decoder_buffer->set_timestamp(timestamp_helper_.GetTimestamp());
+  }
   int frame_count =
       decoder_buffer->data_size() / (sample_size_ * audio_params_.channels());
   timestamp_helper_.AddFrames(frame_count);
diff --git a/chromecast/media/audio/cma_audio_output.h b/chromecast/media/audio/cma_audio_output.h
index 53a0328..037bc0e 100644
--- a/chromecast/media/audio/cma_audio_output.h
+++ b/chromecast/media/audio/cma_audio_output.h
@@ -33,6 +33,8 @@
                  SampleFormat sample_format,
                  const std::string& device_id,
                  const std::string& application_session_id,
+                 bool use_hw_av_sync,
+                 int audio_track_session_id,
                  chromecast::mojom::MultiroomInfoPtr multiroom_info,
                  CmaBackendFactory* cma_backend_factory,
                  CmaBackend::Decoder::Delegate* delegate);
@@ -57,11 +59,13 @@
   void Initialize(SampleFormat sample_format,
                   const std::string& device_id,
                   const std::string& application_session_id,
+                  int audio_track_session_id,
                   chromecast::mojom::MultiroomInfoPtr multiroom_info,
                   CmaBackendFactory* cma_backend_factory);
 
   const ::media::AudioParameters audio_params_;
   const int sample_size_;
+  const bool use_hw_av_sync_;
   CmaBackend::Decoder::Delegate* const delegate_;
 
   ::media::AudioTimestampHelper timestamp_helper_;
diff --git a/chromecast/media/audio/cma_audio_output_stream.cc b/chromecast/media/audio/cma_audio_output_stream.cc
index de2ce5c..bad298e 100644
--- a/chromecast/media/audio/cma_audio_output_stream.cc
+++ b/chromecast/media/audio/cma_audio_output_stream.cc
@@ -63,6 +63,7 @@
   DCHECK_EQ(cma_backend_state_, CmaBackendState::kUninitialized);
   output_ = std::make_unique<CmaAudioOutput>(
       audio_params_, kSampleFormatS16, device_id_, application_session_id,
+      false /*use_hw_av_sync*/, 0 /*audio_track_session_id*/,
       std::move(multiroom_info), cma_backend_factory_, this);
   cma_backend_state_ = CmaBackendState::kStopped;
 
diff --git a/chromecast/media/cma/backend/android/audio_decoder_android.cc b/chromecast/media/cma/backend/android/audio_decoder_android.cc
index 6c23782..87fc4d1 100644
--- a/chromecast/media/cma/backend/android/audio_decoder_android.cc
+++ b/chromecast/media/cma/backend/android/audio_decoder_android.cc
@@ -127,7 +127,8 @@
   DCHECK(IsValidConfig(config_));
   DCHECK(IsValidChannelNumber(config_.channel_number));
   sink_.Reset(this, config_.channel_number, config_.samples_per_second,
-              backend_->Primary(), backend_->DeviceId(),
+              config_.audio_track_session_id, backend_->Primary(),
+              config_.use_hw_av_sync, backend_->DeviceId(),
               backend_->ContentType());
   sink_->SetStreamVolumeMultiplier(volume_multiplier_);
   // Create decoder_ if necessary. This can happen if Stop() was called, and
@@ -291,7 +292,8 @@
 
 void AudioDecoderAndroid::ResetSinkForNewConfig(const AudioConfig& config) {
   sink_.Reset(this, config.channel_number, config.samples_per_second,
-              backend_->Primary(), backend_->DeviceId(),
+              config.audio_track_session_id, backend_->Primary(),
+              config.use_hw_av_sync, backend_->DeviceId(),
               backend_->ContentType());
   sink_->SetStreamVolumeMultiplier(volume_multiplier_);
   pending_output_frames_ = kNoPendingOutput;
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.cc b/chromecast/media/cma/backend/android/audio_sink_android.cc
index 54ccaf6..ad6d40b 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_android.cc
@@ -12,22 +12,6 @@
 namespace media {
 
 // static
-bool AudioSinkAndroid::GetSessionIds(SinkType sink_type,
-                                     int* media_id,
-                                     int* communication_id) {
-  switch (sink_type) {
-    case AudioSinkAndroid::kSinkTypeNativeBased:
-      // TODO(ckuiper): implement a sink using native code.
-      NOTREACHED() << "Native-based audio sink is not implemented yet!";
-      break;
-    case AudioSinkAndroid::kSinkTypeJavaBased:
-      return AudioSinkAndroidAudioTrackImpl::GetSessionIds(media_id,
-                                                           communication_id);
-  }
-  return false;
-}
-
-// static
 int64_t AudioSinkAndroid::GetMinimumBufferedTime(SinkType sink_type,
                                                  const AudioConfig& config) {
   const int64_t kDefaultMinBufferTimeUs = 50000;
@@ -57,7 +41,9 @@
 void ManagedAudioSink::Reset(Delegate* delegate,
                              int num_channels,
                              int samples_per_second,
+                             int audio_track_session_id,
                              bool primary,
+                             bool use_hw_av_sync,
                              const std::string& device_id,
                              AudioContentType content_type) {
   Remove();
@@ -69,9 +55,9 @@
       NOTREACHED() << "Native-based audio sink is not implemented yet!";
       break;
     case AudioSinkAndroid::kSinkTypeJavaBased:
-      sink_ = new AudioSinkAndroidAudioTrackImpl(delegate, num_channels,
-                                                 samples_per_second, primary,
-                                                 device_id, content_type);
+      sink_ = new AudioSinkAndroidAudioTrackImpl(
+          delegate, num_channels, samples_per_second, audio_track_session_id,
+          primary, use_hw_av_sync, device_id, content_type);
   }
   AudioSinkManager::Get()->Add(sink_);
 }
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.h b/chromecast/media/cma/backend/android/audio_sink_android.h
index 9519f6b..2a625617 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.h
+++ b/chromecast/media/cma/backend/android/audio_sink_android.h
@@ -54,14 +54,6 @@
     virtual ~Delegate() {}
   };
 
-  // Gets the Android audio session ids used for media and communication (TTS)
-  // tracks.
-  // Set a return value pointer to null if that id is not needed.
-  // Returns true if the ids populated are valid.
-  static bool GetSessionIds(SinkType sink_type,
-                            int* media_id,
-                            int* communication_id);
-
   static int64_t GetMinimumBufferedTime(SinkType sink_type,
                                         const AudioConfig& config);
 
@@ -124,7 +116,9 @@
   void Reset(Delegate* delegate,
              int num_channels,
              int samples_per_second,
+             int audio_track_session_id,
              bool primary,
+             bool use_hw_av_sync,
              const std::string& device_id,
              AudioContentType content_type);
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
index 47c8f6c..f4a9b0f 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
@@ -47,25 +47,6 @@
 namespace media {
 
 // static
-bool AudioSinkAndroidAudioTrackImpl::GetSessionIds(int* media_id,
-                                                   int* communication_id) {
-  bool is_valid = true;
-  if (media_id) {
-    *media_id = Java_AudioSinkAudioTrackImpl_getSessionIdMedia(
-        base::android::AttachCurrentThread());
-    if (*media_id == -1)
-      is_valid = false;
-  }
-  if (communication_id) {
-    *communication_id = Java_AudioSinkAudioTrackImpl_getSessionIdCommunication(
-        base::android::AttachCurrentThread());
-    if (*communication_id == -1)
-      is_valid = false;
-  }
-  return is_valid;
-}
-
-// static
 int64_t AudioSinkAndroidAudioTrackImpl::GetMinimumBufferedTime(
     int num_channels,
     int samples_per_second) {
@@ -77,13 +58,16 @@
     AudioSinkAndroid::Delegate* delegate,
     int num_channels,
     int input_samples_per_second,
+    int audio_track_session_id,
     bool primary,
+    bool use_hw_av_sync,
     const std::string& device_id,
     AudioContentType content_type)
     : delegate_(delegate),
       num_channels_(num_channels),
       input_samples_per_second_(input_samples_per_second),
       primary_(primary),
+      use_hw_av_sync_(use_hw_av_sync),
       device_id_(device_id),
       content_type_(content_type),
       stream_volume_multiplier_(1.0f),
@@ -98,7 +82,10 @@
   LOG(INFO) << __func__ << "(" << this << "):"
             << " num_channels_=" << num_channels_
             << " input_samples_per_second_=" << input_samples_per_second_
-            << " primary_=" << primary_ << " device_id_=" << device_id_
+            << " audio_track_session_id=" << audio_track_session_id
+            << " primary_=" << primary_
+            << " use_hw_av_sync_=" << use_hw_av_sync_
+            << " device_id_=" << device_id_
             << " content_type_=" << content_type_;
   DCHECK(delegate_);
   DCHECK_GT(num_channels_, 0);
@@ -112,7 +99,7 @@
   Java_AudioSinkAudioTrackImpl_init(
       base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
       static_cast<int>(content_type_), num_channels_, input_samples_per_second_,
-      kDirectBufferSize);
+      kDirectBufferSize, audio_track_session_id, use_hw_av_sync_);
   // Should be set now.
   DCHECK(direct_pcm_buffer_address_);
   DCHECK(direct_rendering_delay_address_);
@@ -197,21 +184,21 @@
     return;
   }
 
-  DVLOG(3) << __func__ << "(" << this << "):"
-           << " [" << pending_data_->data_size() << "]"
-           << " @ts=" << pending_data_->timestamp();
-
   if (pending_data_->data_size() == 0) {
     LOG(INFO) << __func__ << "(" << this << "): empty data buffer!";
     PostPcmCallback(sink_rendering_delay_);
     return;
   }
 
-  ReformatData();
+  pending_data_bytes_after_reformat_ = ReformatData();
+  DVLOG(3) << __func__ << "(" << this << "):"
+           << " [" << pending_data_bytes_after_reformat_ << "]"
+           << " @ts=" << pending_data_->timestamp();
 
   int written = Java_AudioSinkAudioTrackImpl_writePcm(
       base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
-      pending_data_->data_size());
+      pending_data_bytes_after_reformat_,
+      pending_data_->timestamp() * base::Time::kNanosecondsPerMicrosecond);
 
   if (written < 0) {
     LOG(ERROR) << __func__ << "(" << this << "): Cannot write PCM via JNI!";
@@ -219,17 +206,16 @@
     return;
   }
 
-  if (state_ == kStatePaused &&
-      written < static_cast<int>(pending_data_->data_size())) {
+  if (state_ == kStatePaused && written < pending_data_bytes_after_reformat_) {
     LOG(INFO) << "Audio Server is full while in PAUSED, "
               << "will continue when entering PLAY mode.";
     pending_data_bytes_already_fed_ = written;
     return;
   }
 
-  if (written != static_cast<int>(pending_data_->data_size())) {
+  if (written != pending_data_bytes_after_reformat_) {
     LOG(ERROR) << __func__ << "(" << this << "): Wrote " << written
-               << " instead of " << pending_data_->data_size();
+               << " instead of " << pending_data_bytes_after_reformat_;
     // continue anyway, better to do a best-effort than fail completely
   }
 
@@ -265,7 +251,7 @@
   PostPcmCallback(sink_rendering_delay_);
 }
 
-void AudioSinkAndroidAudioTrackImpl::ReformatData() {
+int AudioSinkAndroidAudioTrackImpl::ReformatData() {
   // Data is in planar float format, i.e., planar audio data for stereo is all
   // left samples first, then all right -> "LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR").
   // AudioTrack needs interleaved format -> "LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR").
@@ -279,11 +265,25 @@
     src[c] = reinterpret_cast<const float*>(pending_data_->data()) +
              c * num_of_frames;
   }
-  float* dst = reinterpret_cast<float*>(direct_pcm_buffer_address_);
-  for (int f = 0; f < num_of_frames; f++) {
-    for (int c = 0; c < num_channels_; c++) {
-      *dst++ = *src[c]++;
+  if (use_hw_av_sync_) {
+    // Convert audio data from float to int16_t since hardware av sync audio
+    // track requires ENCODING_PCM_16BIT audio format.
+    int16_t* dst = reinterpret_cast<int16_t*>(direct_pcm_buffer_address_);
+    for (int f = 0; f < num_of_frames; f++) {
+      for (int c = 0; c < num_channels_; c++) {
+        *dst++ = *src[c]++;
+      }
     }
+    return static_cast<int>(pending_data_->data_size()) /
+           (sizeof(float) / sizeof(int16_t));
+  } else {
+    float* dst = reinterpret_cast<float*>(direct_pcm_buffer_address_);
+    for (int f = 0; f < num_of_frames; f++) {
+      for (int c = 0; c < num_channels_; c++) {
+        *dst++ = *src[c]++;
+      }
+    }
+    return static_cast<int>(pending_data_->data_size());
   }
 }
 
@@ -308,17 +308,23 @@
   DCHECK(pending_data_bytes_already_fed_);
 
   int left_to_send =
-      pending_data_->data_size() - pending_data_bytes_already_fed_;
+      pending_data_bytes_after_reformat_ - pending_data_bytes_already_fed_;
   LOG(INFO) << __func__ << "(" << this << "): send remaining " << left_to_send
-            << "/" << pending_data_->data_size();
+            << "/" << pending_data_bytes_after_reformat_;
 
   memmove(direct_pcm_buffer_address_,
           direct_pcm_buffer_address_ + pending_data_bytes_already_fed_,
           left_to_send);
 
+  int bytes_per_frame =
+      num_channels_ * (use_hw_av_sync_ ? sizeof(int16_t) : sizeof(float));
+  int64_t fed_frames = pending_data_bytes_already_fed_ / bytes_per_frame;
+  int64_t timestamp_ns_new = pending_data_->timestamp() +
+                             fed_frames * base::Time::kNanosecondsPerSecond /
+                                 input_samples_per_second_;
   int written = Java_AudioSinkAudioTrackImpl_writePcm(
       base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
-      left_to_send);
+      left_to_send, timestamp_ns_new);
 
   DCHECK(written == left_to_send);
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
index bd7b2a0..3c1c70ed 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
@@ -45,12 +45,6 @@
   AudioSinkAndroidAudioTrackImpl& operator=(
       const AudioSinkAndroidAudioTrackImpl&) = delete;
 
-  // Gets the Android audio session ids used for media and communication (TTS)
-  // tracks.
-  // Set a return value pointer to null if that id is not needed.
-  // Returns true if the ids populated are valid.
-  static bool GetSessionIds(int* media_id, int* communication_id);
-
   static int64_t GetMinimumBufferedTime(int num_channels,
                                         int samples_per_second);
 
@@ -87,7 +81,9 @@
   AudioSinkAndroidAudioTrackImpl(AudioSinkAndroid::Delegate* delegate,
                                  int num_channels,
                                  int input_samples_per_second,
+                                 int audio_track_session_id,
                                  bool primary,
+                                 bool use_hw_av_sync,
                                  const std::string& device_id,
                                  AudioContentType content_type);
 
@@ -101,10 +97,11 @@
   void ScheduleWaitForEosTask();
   void OnPlayoutDone();
 
-  // Reformats audio data from planar float into interleaved float for
-  // AudioTrack. I.e.:
+  // Reformats audio data from planar into interleaved for AudioTrack. I.e.:
   // "LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR" -> "LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR".
-  void ReformatData();
+  // If using hardware av sync, also convert audio data from float to int16_t.
+  // Returns the data size after reformatting.
+  int ReformatData();
 
   void TrackRawMonotonicClockDeviation();
 
@@ -121,6 +118,7 @@
   const int num_channels_;
   const int input_samples_per_second_;
   const bool primary_;
+  const bool use_hw_av_sync_;
   const std::string device_id_;
   const AudioContentType content_type_;
 
@@ -155,6 +153,7 @@
   State state_;
 
   scoped_refptr<DecoderBufferBase> pending_data_;
+  int pending_data_bytes_after_reformat_;
   int pending_data_bytes_already_fed_;
 
   MediaPipelineBackendAndroid::RenderingDelay sink_rendering_delay_;
diff --git a/chromecast/media/cma/backend/android/audio_sink_manager.cc b/chromecast/media/cma/backend/android/audio_sink_manager.cc
index 4b86c343..434681a 100644
--- a/chromecast/media/cma/backend/android/audio_sink_manager.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_manager.cc
@@ -38,12 +38,6 @@
   return AudioSinkAndroid::kSinkTypeJavaBased;
 }
 
-// static
-bool AudioSinkManager::GetSessionIds(int* media_id, int* communication_id) {
-  return AudioSinkAndroid::GetSessionIds(GetDefaultSinkType(), media_id,
-                                         communication_id);
-}
-
 AudioSinkManager::AudioSinkManager() {}
 AudioSinkManager::~AudioSinkManager() {}
 
diff --git a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
index 2452d50..93934e1 100644
--- a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
+++ b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
@@ -90,8 +90,8 @@
 
     // Hardcoded AudioTrack config parameters.
     private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+    private static final int AUDIO_FORMAT_HW_AV_SYNC = AudioFormat.ENCODING_PCM_16BIT;
     private static final int AUDIO_MODE = AudioTrack.MODE_STREAM;
-    private static final int BYTES_PER_SAMPLE = 4; // float (4-bytes)
 
     // Parameter to determine the proper internal buffer size of the AudioTrack instance. In order
     // to minimize latency we want a buffer as small as possible. However, to avoid underruns we
@@ -144,9 +144,6 @@
 
     private static AudioManager sAudioManager;
 
-    private static int sSessionIdMedia = AudioManager.ERROR;
-    private static int sSessionIdCommunication = AudioManager.ERROR;
-
     private final long mNativeAudioSinkAudioTrackImpl;
 
     private String mTag = TAG;
@@ -178,8 +175,10 @@
     private boolean mIsInitialized;
 
     // Dynamic AudioTrack config parameter.
+    private boolean mUseHwAvSync;
     private int mSampleRateInHz;
     private int mChannelCount;
+    private int mSampleSize;
 
     private AudioTrack mAudioTrack;
 
@@ -256,41 +255,30 @@
         }
     }
 
+    private static int getSampleSize(int sampleFormat) {
+        switch (sampleFormat) {
+            case AudioFormat.ENCODING_PCM_8BIT:
+                return 1;
+            case AudioFormat.ENCODING_PCM_16BIT:
+                return 2;
+            case AudioFormat.ENCODING_PCM_24BIT_PACKED:
+                return 3;
+            case AudioFormat.ENCODING_PCM_32BIT:
+            case AudioFormat.ENCODING_PCM_FLOAT:
+            default:
+                return 4;
+        }
+    }
+
     @CalledByNative
     public static long getMinimumBufferedTime(int channelCount, int sampleRateInHz) {
         int sizeBytes = AudioTrack.getMinBufferSize(
                 sampleRateInHz, getChannelConfig(channelCount), AUDIO_FORMAT);
         long sizeUs = SEC_IN_USEC * (long) sizeBytes
-                / (BYTES_PER_SAMPLE * channelCount * (long) sampleRateInHz);
+                / (getSampleSize(AUDIO_FORMAT) * channelCount * (long) sampleRateInHz);
         return sizeUs + MIN_BUFFERED_TIME_PADDING_US;
     }
 
-    @CalledByNative
-    public static int getSessionIdMedia() {
-        if (sSessionIdMedia == AudioManager.ERROR) {
-            sSessionIdMedia = getAudioManager().generateAudioSessionId();
-            if (sSessionIdMedia == AudioManager.ERROR) {
-                Log.e(TAG, "Cannot generate session-id for media tracks!");
-            } else {
-                Log.i(TAG, "Session-id for media tracks is " + sSessionIdMedia);
-            }
-        }
-        return sSessionIdMedia;
-    }
-
-    @CalledByNative
-    public static int getSessionIdCommunication() {
-        if (sSessionIdCommunication == AudioManager.ERROR) {
-            sSessionIdCommunication = getAudioManager().generateAudioSessionId();
-            if (sSessionIdCommunication == AudioManager.ERROR) {
-                Log.e(TAG, "Cannot generate session-id for communication tracks!");
-            } else {
-                Log.i(TAG, "Session-id for communication tracks is " + sSessionIdCommunication);
-            }
-        }
-        return sSessionIdCommunication;
-    }
-
     /** Construction */
     @CalledByNative
     private static AudioSinkAudioTrackImpl createAudioSinkAudioTrackImpl(
@@ -325,13 +313,17 @@
         return (SEC_IN_NSEC * numOfFrames + mSampleRateInHz / 2) / mSampleRateInHz;
     }
 
+    private boolean isValidSessionId(int sessionId) {
+        return sessionId > 0;
+    }
+
     /**
      * Initializes the instance by creating the AudioTrack object and allocating
      * the shared memory buffers.
      */
     @CalledByNative
     private void init(@AudioContentType int castContentType, int channelCount, int sampleRateInHz,
-            int bytesPerBuffer) {
+            int bytesPerBuffer, int sessionId, boolean useHwAvSync) {
         mTag = TAG + "(" + castContentType + ":" + (sInstanceCounter++) + ")";
 
         // Setup throttled logs: pass the first 5, then every 1sec, reset after 5.
@@ -353,25 +345,21 @@
             Log.e(mTag, "Invalid sampleRateInHz=" + sampleRateInHz + " given!");
             return;
         }
+        mUseHwAvSync = useHwAvSync;
+        int audioEncodingFormat = mUseHwAvSync ? AUDIO_FORMAT_HW_AV_SYNC : AUDIO_FORMAT;
         mSampleRateInHz = sampleRateInHz;
         mChannelCount = channelCount;
+        mSampleSize = getSampleSize(audioEncodingFormat);
 
         int usageType = CAST_TYPE_TO_ANDROID_USAGE_TYPE_MAP.get(castContentType);
-        int contentType = CAST_TYPE_TO_ANDROID_CONTENT_TYPE_MAP.get(castContentType);
-
-        int sessionId = AudioManager.ERROR;
-        if (castContentType == AudioContentType.MEDIA) {
-            sessionId = getSessionIdMedia();
-        } else if (castContentType == AudioContentType.COMMUNICATION) {
-            sessionId = getSessionIdCommunication();
-        }
-        // AudioContentType.ALARM doesn't get a sessionId.
+        int contentType = mUseHwAvSync ? AudioAttributes.CONTENT_TYPE_MOVIE
+                                       : CAST_TYPE_TO_ANDROID_CONTENT_TYPE_MAP.get(castContentType);
 
         int channelConfig = getChannelConfig(mChannelCount);
         int bufferSizeInBytes = MIN_BUFFER_SIZE_MULTIPLIER
-                * AudioTrack.getMinBufferSize(mSampleRateInHz, channelConfig, AUDIO_FORMAT);
+                * AudioTrack.getMinBufferSize(mSampleRateInHz, channelConfig, audioEncodingFormat);
         int bufferSizeInMs =
-                1000 * bufferSizeInBytes / (BYTES_PER_SAMPLE * mChannelCount * mSampleRateInHz);
+                1000 * bufferSizeInBytes / (mSampleSize * mChannelCount * mSampleRateInHz);
         Log.i(mTag,
                 "Init: create an AudioTrack of size=" + bufferSizeInBytes + " (" + bufferSizeInMs
                         + "ms) usageType=" + usageType + " contentType=" + contentType
@@ -381,31 +369,33 @@
             // Retry if AudioTrack creation fails.
             int retries = 0;
             do {
+                AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+                attributesBuilder.setContentType(contentType).setUsage(usageType);
+                if (mUseHwAvSync) {
+                    attributesBuilder.setFlags(AudioAttributes.FLAG_HW_AV_SYNC);
+                }
                 AudioTrack.Builder builder = new AudioTrack.Builder();
                 builder.setBufferSizeInBytes(bufferSizeInBytes)
                         .setTransferMode(AUDIO_MODE)
-                        .setAudioAttributes(new AudioAttributes.Builder()
-                                                    .setUsage(usageType)
-                                                    .setContentType(contentType)
-                                                    .build())
+                        .setAudioAttributes(attributesBuilder.build())
                         .setAudioFormat(new AudioFormat.Builder()
-                                                .setEncoding(AUDIO_FORMAT)
+                                                .setEncoding(audioEncodingFormat)
                                                 .setSampleRate(mSampleRateInHz)
                                                 .setChannelMask(channelConfig)
                                                 .build());
-                if (sessionId != AudioManager.ERROR) builder.setSessionId(sessionId);
+                if (isValidSessionId(sessionId)) builder.setSessionId(sessionId);
                 mAudioTrack = builder.build();
             } while (mAudioTrack == null && retries++ < MAX_RETRIES_FOR_AUDIO_TRACKS);
         } else {
             // Using pre-M API.
-            if (sessionId == AudioManager.ERROR) {
+            if (isValidSessionId(sessionId)) {
                 mAudioTrack = new AudioTrack(CAST_TYPE_TO_ANDROID_STREAM_TYPE.get(castContentType),
-                        mSampleRateInHz, channelConfig, AUDIO_FORMAT, bufferSizeInBytes,
-                        AudioTrack.MODE_STREAM);
+                        mSampleRateInHz, channelConfig, audioEncodingFormat, bufferSizeInBytes,
+                        AUDIO_MODE, sessionId);
             } else {
                 mAudioTrack = new AudioTrack(CAST_TYPE_TO_ANDROID_STREAM_TYPE.get(castContentType),
-                        mSampleRateInHz, channelConfig, AUDIO_FORMAT, bufferSizeInBytes,
-                        AudioTrack.MODE_STREAM, sessionId);
+                        mSampleRateInHz, channelConfig, audioEncodingFormat, bufferSizeInBytes,
+                        AUDIO_MODE);
             }
         }
 
@@ -530,7 +520,7 @@
      * error.
      */
     @CalledByNative
-    private int writePcm(int sizeInBytes) {
+    private int writePcm(int sizeInBytes, long timestampNs) {
         if (DEBUG_LEVEL >= 3) {
             Log.i(mTag,
                     "Writing new PCM data:"
@@ -552,7 +542,13 @@
 
         // Feed into AudioTrack - blocking call.
         long beforeMsecs = SystemClock.elapsedRealtime();
-        int bytesWritten = mAudioTrack.write(mPcmBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING);
+        int bytesWritten;
+        if (mUseHwAvSync) {
+            bytesWritten = mAudioTrack.write(
+                    mPcmBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING, timestampNs);
+        } else {
+            bytesWritten = mAudioTrack.write(mPcmBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING);
+        }
 
         if (bytesWritten < 0) {
             int error = bytesWritten;
@@ -569,8 +565,14 @@
             int bytesLeft = sizeInBytes - bytesWritten;
             if (bytesLeft > 0) {
                 mPcmBuffer.position(bytesWritten);
-                int moreBytesWritten =
-                        mAudioTrack.write(mPcmBuffer, bytesLeft, AudioTrack.WRITE_BLOCKING);
+                int moreBytesWritten;
+                if (mUseHwAvSync) {
+                    moreBytesWritten = mAudioTrack.write(
+                            mPcmBuffer, bytesLeft, AudioTrack.WRITE_BLOCKING, timestampNs);
+                } else {
+                    moreBytesWritten =
+                            mAudioTrack.write(mPcmBuffer, bytesLeft, AudioTrack.WRITE_BLOCKING);
+                }
                 if (moreBytesWritten < 0) {
                     int error = moreBytesWritten;
                     Log.e(mTag, "Couldn't write into AudioTrack (" + error + ")");
@@ -580,13 +582,13 @@
             }
         }
 
-        int framesWritten = bytesWritten / (BYTES_PER_SAMPLE * mChannelCount);
+        int framesWritten = bytesWritten / (mSampleSize * mChannelCount);
         mTotalFramesWritten += framesWritten;
 
         if (DEBUG_LEVEL >= 3) {
             Log.i(mTag,
                     "  wrote " + bytesWritten + "/" + sizeInBytes + " total_bytes_written="
-                            + (mTotalFramesWritten * BYTES_PER_SAMPLE * mChannelCount)
+                            + (mTotalFramesWritten * mSampleSize * mChannelCount)
                             + " took:" + (SystemClock.elapsedRealtime() - beforeMsecs) + "ms");
         }
 
diff --git a/chromecast/public/media/decoder_config.h b/chromecast/public/media/decoder_config.h
index 67e572d..a2a14af5 100644
--- a/chromecast/public/media/decoder_config.h
+++ b/chromecast/public/media/decoder_config.h
@@ -302,6 +302,13 @@
   std::vector<uint8_t> extra_data;
   // Encryption scheme (if any) used for the content.
   EncryptionScheme encryption_scheme;
+  // Hardware AV sync flag.
+  bool use_hw_av_sync;
+  // The session id which the Android AudioTrack will be attached to. If not
+  // valid, a new one will be atomically generated by the Android system.
+  // For Cast Connect Multizone use case, if the app uses hardware av sync,
+  // mediashell will get the id of the media session opened by the app.
+  int audio_track_session_id;
 };
 
 inline AudioConfig::AudioConfig()
@@ -312,7 +319,9 @@
       bytes_per_channel(0),
       channel_number(0),
       samples_per_second(0),
-      encryption_scheme(EncryptionScheme::kUnencrypted) {}
+      encryption_scheme(EncryptionScheme::kUnencrypted),
+      use_hw_av_sync(false),
+      audio_track_session_id(0) {}
 inline AudioConfig::AudioConfig(const AudioConfig& other) = default;
 inline AudioConfig::~AudioConfig() {
 }
diff --git a/chromeos/crosapi/mojom/dlp.mojom b/chromeos/crosapi/mojom/dlp.mojom
index df23dee7..4b0f3cd 100644
--- a/chromeos/crosapi/mojom/dlp.mojom
+++ b/chromeos/crosapi/mojom/dlp.mojom
@@ -4,6 +4,7 @@
 
 module crosapi.mojom;
 
+import "mojo/public/mojom/base/string16.mojom";
 import "url/mojom/url.mojom";
 
 // Corresponds to DlpRulesManager::Level enum.
@@ -13,6 +14,7 @@
   kReport = 1,
   kWarn = 2,
   kBlock = 3,
+  [MinVersion=2] kNotSet = 4,
 };
 
 // Corresponds to RestrictionLevelAndUrl struct.
@@ -31,10 +33,44 @@
   DlpRestrictionLevelAndUrl screen_share@3;
 };
 
+// Captured area.
+[Stable, Extensible]
+struct ScreenShareArea {
+  // All root windows if |window_id| is null.
+  string? window_id;
+};
+
+// Callback to control the state (pause/resume) of a remote screen share.
+[Stable]
+interface StateChangeDelegate {
+  // Request to pause the screen share.
+  OnPause@0();
+  // Request to resume the screen share.
+  OnResume@1();
+  // Request to stop the screen share. Should be called only once.
+  OnStop@2();
+};
+
 // Interface to communicate from Lacros DLP layer to Ash. Used to notify Ash
 // about changes in restrictions applied to web content in Lacros.
+//
+// Next MinVersion: 3
+// Next ID: 4
 [Stable, Uuid="793ee9c6-3873-4ea8-a541-894412ddfc0b"]
 interface Dlp {
   // Called when |restrictions| are changed for window with |window_id|.
   DlpRestrictionsUpdated@0(string window_id, DlpRestrictionSet restrictions);
+
+  // Called to evaluate screenshare restriction for a window or full screen.
+  [MinVersion=1] CheckScreenShareRestriction@1(
+      ScreenShareArea area, mojo_base.mojom.String16 application_title) =>
+          (bool proceed);
+
+  // Called when screen share in Lacros is started.
+  [MinVersion=2] OnScreenShareStarted@2(string label, ScreenShareArea area,
+      mojo_base.mojom.String16 application_title,
+      pending_remote<StateChangeDelegate> delegate);
+
+  // Called when screen share in Lacros is stopped.
+  [MinVersion=2] OnScreenShareStopped@3(string label, ScreenShareArea area);
 };
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 6812d3e..9c023e4 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-99-4815.0-1642417637-benchmark-99.0.4837.0-r1.orderfile.xz
+chromeos-chrome-orderfile-field-99-4815.0-1642417637-benchmark-99.0.4840.0-r1.orderfile.xz
diff --git a/chromeos/services/bluetooth_config/discovery_session_manager_impl.cc b/chromeos/services/bluetooth_config/discovery_session_manager_impl.cc
index 34233e3..3248b3c 100644
--- a/chromeos/services/bluetooth_config/discovery_session_manager_impl.cc
+++ b/chromeos/services/bluetooth_config/discovery_session_manager_impl.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/bluetooth_config/discovery_session_manager_impl.h"
 
 #include "chromeos/services/bluetooth_config/device_pairing_handler_impl.h"
+#include "components/device_event_log/device_event_log.h"
 #include "device/bluetooth/bluetooth_discovery_session.h"
 
 namespace chromeos {
@@ -74,6 +75,8 @@
   if (is_discovery_attempt_in_progress_)
     return;
 
+  BLUETOOTH_LOG(DEBUG) << "Starting discovery session";
+
   is_discovery_attempt_in_progress_ = true;
   bluetooth_adapter_->StartDiscoverySession(
       kDiscoveryClientName,
@@ -108,6 +111,8 @@
 }
 
 void DiscoverySessionManagerImpl::DestroyDiscoverySession() {
+  BLUETOOTH_LOG(DEBUG) << "Destroying discovery session";
+
   discovery_session_.reset();
   NotifyDiscoveryStoppedAndClearActiveClients();
 }
diff --git a/components/autofill_assistant/browser/batch_element_checker.cc b/components/autofill_assistant/browser/batch_element_checker.cc
index 527ab78..b4df6c83 100644
--- a/components/autofill_assistant/browser/batch_element_checker.cc
+++ b/components/autofill_assistant/browser/batch_element_checker.cc
@@ -4,15 +4,19 @@
 
 #include "components/autofill_assistant/browser/batch_element_checker.h"
 
+#include <cstddef>
 #include <utility>
 
 #include "base/bind.h"
 #include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "components/autofill_assistant/browser/web/element_action_util.h"
 #include "components/autofill_assistant/browser/web/element_finder.h"
+#include "components/autofill_assistant/browser/web/selector_observer.h"
 #include "components/autofill_assistant/browser/web/web_controller.h"
 
 namespace autofill_assistant {
@@ -46,10 +50,9 @@
   check.proto = condition;
   check.callback = std::move(callback);
   element_condition_checks_.emplace_back(std::move(check));
-  size_t index = element_condition_checks_.size() - 1;
-  AddElementConditionResults(element_condition_checks_[index].proto, index);
 }
 
+// TODO(b/215335501): Refactor out of BatchElementChecker.
 void BatchElementChecker::AddFieldValueCheck(const Selector& selector,
                                              GetFieldValueCallback callback) {
   DCHECK(!started_);
@@ -72,12 +75,30 @@
   all_done_.emplace_back(std::move(all_done));
 }
 
+void BatchElementChecker::EnableObserver(
+    base::TimeDelta max_wait_time,
+    base::TimeDelta periodic_check_interval) {
+  DCHECK(!use_observers_);
+  DCHECK(!started_);
+  DCHECK(get_field_value_callbacks_.empty())
+      << "Observer-based BatchElementChecker doesn't work with "
+         "AddFieldValueCheck";
+
+  use_observers_ = true;
+  observer_max_wait_time_ = max_wait_time;
+  observer_periodic_check_interval_ = periodic_check_interval;
+}
+
 void BatchElementChecker::Run(WebController* web_controller) {
   DCHECK(web_controller);
   DCHECK(!started_);
   for (size_t i = 0; i < element_condition_checks_.size(); ++i) {
     AddElementConditionResults(element_condition_checks_[i].proto, i);
   }
+  if (use_observers_) {
+    RunWithObserver(web_controller);
+    return;
+  }
   started_ = true;
 
   pending_checks_count_ =
@@ -122,6 +143,135 @@
   CheckDone();
 }
 
+void BatchElementChecker::RunWithObserver(WebController* web_controller) {
+  DCHECK(get_field_value_callbacks_.empty())
+      << "Observer-based BatchElementChecker doesn't work with "
+         "AddFieldValueCheck";
+  DCHECK(!started_);
+  std::vector<SelectorObserver::ObservableSelector> selectors;
+
+  size_t index = 0;
+  for (auto& entry : unique_selectors_) {
+    selectors.emplace_back(SelectorObserver::SelectorId(index++),
+                           /* proto = */ entry.first.first.proto,
+                           /* strict = */ entry.first.second);
+  }
+  if (selectors.size() == 0) {
+    FinishedCallbacks();
+    return;
+  }
+  started_ = true;
+  auto result = web_controller->ObserveSelectors(
+      selectors, observer_max_wait_time_, observer_periodic_check_interval_,
+      base::BindRepeating(&BatchElementChecker::OnResultsUpdated,
+                          weak_ptr_factory_.GetWeakPtr())
+
+  );
+  if (!result.ok()) {
+    CallAllCallbacksWithError(result);
+  }
+}
+
+void BatchElementChecker::OnResultsUpdated(
+    const ClientStatus& status,
+    const std::vector<SelectorObserver::Update>& updates,
+    SelectorObserver* selector_observer) {
+  if (!status.ok()) {
+    CallAllCallbacksWithError(status);
+    return;
+  }
+  std::vector<size_t> updated_conditions_vector;
+  // Apply updates
+  for (auto& update : updates) {
+    size_t selector_id = update.selector_id.value();
+    DCHECK_LT(selector_id, unique_selectors_.size());
+    auto& affected_results =
+        std::next(unique_selectors_.begin(), selector_id)->second;
+    for (auto& pair : affected_results) {
+      size_t condition_index = pair.first;
+      size_t result_index = pair.second;
+      auto& condition = element_condition_checks_[condition_index];
+      auto& result = condition.results[result_index];
+      result.match = {/* has_value= */ true, /* value= */ update.match};
+      result.element_id = update.match ? update.element_id : -1;
+      updated_conditions_vector.emplace_back(condition_index);
+    }
+  }
+  auto updated_conditions =
+      base::flat_set<size_t>(std::move(updated_conditions_vector));
+
+  bool any_match = false;
+  for (size_t condition_index : updated_conditions) {
+    auto& condition = element_condition_checks_[condition_index];
+    size_t index = 0;
+    if (EvaluateElementPrecondition(condition.proto, condition.results, &index,
+                                    nullptr)
+            .matches()) {
+      any_match = true;
+      break;
+    }
+  }
+  if (!any_match) {
+    selector_observer->Continue();
+    return;
+  }
+  std::vector<SelectorObserver::RequestedElement> wanted_elements;
+  size_t index = 0;
+  for (auto& entry : unique_selectors_) {
+    for (auto& pair : entry.second) {
+      size_t condition_index = pair.first;
+      size_t result_index = pair.second;
+      auto& condition = element_condition_checks_[condition_index];
+      auto& result = condition.results[result_index];
+      if (result.match.matches() && result.client_id) {
+        wanted_elements.emplace_back(SelectorObserver::SelectorId(index),
+                                     result.element_id);
+      }
+    }
+    ++index;
+  }
+  selector_observer->GetElementsAndStop(
+      wanted_elements, base::BindOnce(&BatchElementChecker::OnGetElementsDone,
+                                      weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BatchElementChecker::OnGetElementsDone(
+    const ClientStatus& status,
+    const base::flat_map<SelectorObserver::SelectorId, DomObjectFrameStack>&
+        elements) {
+  if (!status.ok()) {
+    CallAllCallbacksWithError(status);
+    return;
+  }
+  for (auto& element : elements) {
+    size_t results_index = element.first.value();
+    DCHECK_LT(results_index, unique_selectors_.size());
+    auto& affected_results =
+        std::next(unique_selectors_.begin(), results_index)->second;
+    for (auto& pair : affected_results) {
+      size_t condition_index = pair.first;
+      size_t result_index = pair.second;
+      DCHECK(condition_index < element_condition_checks_.size());
+      auto& condition = element_condition_checks_[condition_index];
+      DCHECK(result_index < condition.results.size());
+      auto& result = condition.results[result_index];
+      DCHECK(result.client_id.has_value());
+      condition.elements[result.client_id.value()] = element.second;
+    }
+  }
+  CheckElementConditions();
+  FinishedCallbacks();
+}
+
+void BatchElementChecker::CallAllCallbacksWithError(
+    const ClientStatus& status) {
+  DCHECK(!status.ok());
+  for (auto& entry : element_condition_checks_) {
+    std::move(entry.callback).Run(status, {}, {});
+  }
+  FinishedCallbacks();
+}
+
 void BatchElementChecker::OnSelectorChecked(
     std::vector<std::pair</* element_condition_index */ size_t,
                           /* result_index */ size_t>>* results,
@@ -134,7 +284,7 @@
     auto& condition = element_condition_checks_[condition_index];
     DCHECK(result_index < condition.results.size());
     Result& result = condition.results[result_index];
-    result.match = element_status.ok();
+    result.match = {/* has_value= */ true, /* value= */ element_status.ok()};
     // TODO(szermatt): Consider reporting element_status as an unexpected error
     // right away if it is neither success nor ELEMENT_RESOLUTION_FAILED.
     if (element_status.ok() && result.client_id.has_value()) {
@@ -160,12 +310,16 @@
   DCHECK_GE(pending_checks_count_, 0);
   if (pending_checks_count_ <= 0) {
     CheckElementConditions();
-    std::vector<base::OnceCallback<void()>> all_done = std::move(all_done_);
-    // Callbacks in all_done_ can delete the current instance. Nothing can
-    // safely access |this| after this point.
-    for (auto& callback : all_done) {
-      std::move(callback).Run();
-    }
+    FinishedCallbacks();
+  }
+}
+
+void BatchElementChecker::FinishedCallbacks() {
+  std::vector<base::OnceCallback<void()>> all_done = std::move(all_done_);
+  // Callbacks in all_done_ can delete the current instance. Nothing can
+  // safely access |this| after this point.
+  for (auto& callback : all_done) {
+    std::move(callback).Run();
   }
 }
 
@@ -173,8 +327,9 @@
   for (auto& check : element_condition_checks_) {
     std::vector<std::string> payloads;
     size_t index = 0;
-    const auto match = EvaluateElementPrecondition(check.proto, check.results,
-                                                   &index, &payloads);
+    bool match = EvaluateElementPrecondition(check.proto, check.results, &index,
+                                             &payloads)
+                     .matches();
     std::move(check.callback)
         .Run(match ? ClientStatus(ACTION_APPLIED)
                    : ClientStatus(ELEMENT_RESOLUTION_FAILED),
@@ -182,20 +337,24 @@
   }
 }
 
-bool BatchElementChecker::EvaluateElementPrecondition(
+BatchElementChecker::MatchResult
+BatchElementChecker::EvaluateElementPrecondition(
     const ElementConditionProto& proto,
     const std::vector<Result>& results,
     size_t* next_result_index,
     std::vector<std::string>* payloads) {
-  bool match = false;
+  MatchResult match{/* checked = */ true, /* match_result= */ false};
   switch (proto.type_case()) {
     case ElementConditionProto::kAllOf: {
-      match = true;
+      match.match_result = true;
       for (const ElementConditionProto& condition :
            proto.all_of().conditions()) {
-        if (!EvaluateElementPrecondition(condition, results, next_result_index,
-                                         payloads)) {
-          match = false;
+        MatchResult result = EvaluateElementPrecondition(
+            condition, results, next_result_index, payloads);
+        if (match.checked && result.checked) {
+          match.match_result = match.match_result && result.match_result;
+        } else {
+          match.checked = false;
         }
       }
       break;
@@ -204,20 +363,24 @@
       for (const ElementConditionProto& condition :
            proto.any_of().conditions()) {
         if (EvaluateElementPrecondition(condition, results, next_result_index,
-                                        payloads)) {
-          match = true;
+                                        payloads)
+                .matches()) {
+          match.match_result = true;
         }
       }
       break;
     }
 
     case ElementConditionProto::kNoneOf: {
-      match = true;
+      match.match_result = true;
       for (const ElementConditionProto& condition :
            proto.none_of().conditions()) {
-        if (EvaluateElementPrecondition(condition, results, next_result_index,
-                                        payloads)) {
-          match = false;
+        MatchResult result = EvaluateElementPrecondition(
+            condition, results, next_result_index, payloads);
+        if (match.checked && result.checked) {
+          match.match_result = match.match_result && !result.match_result;
+        } else {
+          match.checked = false;
         }
       }
       break;
@@ -236,10 +399,10 @@
     }
 
     case ElementConditionProto::TYPE_NOT_SET:
-      match = true;  // An empty condition is true
+      match.match_result = true;  // An empty condition is true
       break;
   }
-  if (match && !proto.payload().empty()) {
+  if (payloads && match.matches() && !proto.payload().empty()) {
     payloads->emplace_back(proto.payload());
   }
   return match;
@@ -281,7 +444,7 @@
       result.strict = proto.require_unique_element();
       if (result.selector.empty()) {
         // Empty selectors never match.
-        result.match = false;
+        result.match = {/* has_value= */ true, /* value= */ false};
       } else {
         unique_selectors_[std::make_pair(result.selector, result.strict)]
             .emplace_back(element_condition_index, results.size() - 1);
diff --git a/components/autofill_assistant/browser/batch_element_checker.h b/components/autofill_assistant/browser/batch_element_checker.h
index d129af1..c015a62 100644
--- a/components/autofill_assistant/browser/batch_element_checker.h
+++ b/components/autofill_assistant/browser/batch_element_checker.h
@@ -20,6 +20,7 @@
 #include "components/autofill_assistant/browser/service.pb.h"
 #include "components/autofill_assistant/browser/web/element.h"
 #include "components/autofill_assistant/browser/web/element_finder.h"
+#include "components/autofill_assistant/browser/web/selector_observer.h"
 
 namespace autofill_assistant {
 class WebController;
@@ -79,25 +80,33 @@
   // Returns true if all there are no checks to run.
   bool empty() const;
 
+  // Turns on observer mode. When BatchElementChecker runs in observer mode, it
+  // waits until any element condition checks or element checks become true.
+  void EnableObserver(base::TimeDelta max_wait_time,
+                      base::TimeDelta periodic_check_interval);
+
   // Runs the checks. Once all checks are done, calls the callbacks registered
   // to AddAllDoneCallback().
   void Run(WebController* web_controller);
 
-  // Awaits for element conditions
-  void AwaitConditions(WebController* web_controller);
-
  private:
-  // For ElementPreconditionChecks, results of one independent selector within
-  // one element precondition
+  // Not using absl::optional because it's hard to see what "if (var)" means.
+  struct MatchResult {
+    bool checked = false;
+    bool match_result = false;
+    bool matches() { return checked && match_result; }
+  };
+
+  // Results of one independent |Selector| within one |ElementCondition|.
   struct Result {
     Result();
     ~Result();
     Result(const Result&);
 
-    // Selector checked
+    // Selector checked.
     Selector selector;
-    // Result of checking that selector
-    bool match = false;
+    // Result of checking that selector.
+    MatchResult match{/* checked= */ false, /* match_result= */ false};
 
     // The identifier given to this result through the script. This identifier
     // can be used to later find the element in the |ElementStore|.
@@ -105,30 +114,36 @@
 
     // Whether the matching should be done strict or not.
     bool strict = false;
+
+    // Used in SelectorObserver runs, element_id to retrieve result from
+    // SelectorObserver.
+    int element_id = -1;
   };
 
-  // For ElementConditionChecks
   struct ElementConditionCheck {
     ElementConditionCheck();
     ~ElementConditionCheck();
     ElementConditionCheck(ElementConditionCheck&&);
 
-    // Result of individual Selector within the ElementCondition.
+    // Result of individual |Selector|s within the |ElementCondition|.
     std::vector<Result> results;
 
-    // Callback called with the result after the check is done (if using Run())
-    // or after one condition matches (if using AwaitAny()).
+    // Callback called with the result after the check is done (if observer
+    // mode is enabled) or after one condition matches (if observer mode
+    // is disabled).
     ElementConditionCheckCallback callback;
 
-    // ElementConditionProto to check.
+    // |ElementConditionProto| to check.
     ElementConditionProto proto;
 
-    // Resulting found elements. Key is the client_id in the Match
-    // ElementConditions (used to refer to this element in the scripts), value
+    // Resulting found elements. Key is the |client_id| in the |Match|
+    // |ElementCondition|s (used to refer to this element in the scripts), value
     // is the found element.
     base::flat_map<std::string, DomObjectFrameStack> elements;
   };
 
+  void RunWithObserver(WebController* web_controller);
+
   // Gets called for each Selector checked.
   void OnSelectorChecked(
       std::vector<std::pair</* element_condition_index */ size_t,
@@ -146,26 +161,37 @@
                            const std::string& value);
 
   void CheckDone();
+  void FinishedCallbacks();
+  void CallAllCallbacksWithError(const ClientStatus& status);
 
   // Add selectors from |proto| to |results_|, doing a depth-first search.
   void AddElementConditionResults(const ElementConditionProto& proto,
                                   size_t element_condition_index);
 
-  bool EvaluateElementPrecondition(const ElementConditionProto& proto_,
-                                   const std::vector<Result>& results,
-                                   size_t* results_iter,
-                                   std::vector<std::string>* payloads);
+  MatchResult EvaluateElementPrecondition(const ElementConditionProto& proto,
+                                          const std::vector<Result>& results,
+                                          size_t* results_iter,
+                                          std::vector<std::string>* payloads);
   void CheckElementConditions();
+  void OnResultsUpdated(const ClientStatus& status,
+                        const std::vector<SelectorObserver::Update>& updates,
+                        SelectorObserver* selector_observer);
+
+  void OnGetElementsDone(const ClientStatus& status,
+                         const base::flat_map<SelectorObserver::SelectorId,
+                                              DomObjectFrameStack>& elements);
 
   // A way to find unique selectors and what |Result|'s are affected by them.
   //
-  // Must not be modified after Run() is called because it's referenced by
+  // Must not be modified after |Run()| is called because it's referenced by
   // index.
   base::flat_map<std::pair<Selector, /* strict */ bool>,
                  std::vector<std::pair</* element_condition_index */ size_t,
                                        /* result_index */ size_t>>>
       unique_selectors_;
 
+  // Must not be modified after |Run()| is called because it's referenced by
+  // index.
   std::vector<ElementConditionCheck> element_condition_checks_;
 
   // A map of GetFieldValue arguments (selector) to callbacks that take the
@@ -177,6 +203,11 @@
   // Run() was called. Checking elements might or might not have finished yet.
   bool started_ = false;
 
+  // Whether to wait until one of the conditions becomes true.
+  bool use_observers_ = false;
+  base::TimeDelta observer_max_wait_time_;
+  base::TimeDelta observer_periodic_check_interval_;
+
   std::vector<base::OnceCallback<void()>> all_done_;
 
   base::WeakPtrFactory<BatchElementChecker> weak_ptr_factory_{this};
diff --git a/components/autofill_assistant/browser/client_context.h b/components/autofill_assistant/browser/client_context.h
index fa3a1728..d798fdb 100644
--- a/components/autofill_assistant/browser/client_context.h
+++ b/components/autofill_assistant/browser/client_context.h
@@ -19,7 +19,7 @@
   // Updates the client context based on the current state of the client.
   virtual void Update(const TriggerContext& trigger_context) = 0;
   // Updates the payments client token. This is not part of the normal update.
-  virtual void SetPaymentsClientToken(const std::string& client_token);
+  virtual void SetPaymentsClientToken(const std::string& client_token) = 0;
   // Returns the proto representation of this client context.
   virtual ClientContextProto AsProto() const = 0;
 };
diff --git a/components/autofill_assistant/browser/web/web_controller_browsertest.cc b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
index 9e08279..698e3999 100644
--- a/components/autofill_assistant/browser/web/web_controller_browsertest.cc
+++ b/components/autofill_assistant/browser/web/web_controller_browsertest.cc
@@ -862,6 +862,86 @@
         /* node_frame_id= */ std::string());
   }
 
+  static ElementConditionProto AllOfConditions(
+      std::vector<ElementConditionProto> conditions) {
+    ElementConditionProto proto;
+    for (auto& condition : conditions) {
+      proto.mutable_any_of()->mutable_conditions()->Add(std::move(condition));
+    }
+    return proto;
+  }
+
+  static ElementConditionProto AnyOfConditions(
+      std::vector<ElementConditionProto> conditions) {
+    ElementConditionProto proto;
+    for (auto& condition : conditions) {
+      proto.mutable_any_of()->mutable_conditions()->Add(std::move(condition));
+    }
+    return proto;
+  }
+
+  static ElementConditionProto NoneOfConditions(
+      std::vector<ElementConditionProto> conditions) {
+    ElementConditionProto proto;
+    for (auto& condition : conditions) {
+      proto.mutable_none_of()->mutable_conditions()->Add(std::move(condition));
+    }
+    return proto;
+  }
+
+  static ElementConditionProto Match(Selector selector, bool strict = false) {
+    ElementConditionProto proto;
+    *proto.mutable_match() = selector.proto;
+    proto.set_require_unique_element(strict);
+    return proto;
+  }
+
+  // Run Observer BatchElementChecker on the provided conditions. The second
+  // value in the pairs (bool) is the match expectation.
+  void RunObserverBatchElementChecker(
+      const std::vector<std::pair<ElementConditionProto, bool>>& conditions) {
+    base::RunLoop run_loop;
+    BatchElementChecker checker;
+    std::vector<bool> actual_results(conditions.size(), false);
+    std::vector<bool> expected_results(conditions.size(), false);
+
+    for (size_t i = 0; i < conditions.size(); ++i) {
+      expected_results[i] = conditions[i].second;
+      checker.AddElementConditionCheck(
+          conditions[i].first,
+          base::BindOnce(&WebControllerBrowserTest::
+                             ObserverBatchElementCheckerElementCallback,
+                         &actual_results, i));
+    }
+    checker.AddAllDoneCallback(base::BindOnce(
+        &WebControllerBrowserTest::ObserverBatchElementCheckerAllDoneCallback,
+        run_loop.QuitClosure(), &expected_results, &actual_results));
+
+    checker.EnableObserver(base::Milliseconds(30000), base::Milliseconds(1000));
+    checker.Run(web_controller_.get());
+    run_loop.Run();
+    EXPECT_EQ(web_controller_->pending_workers_.size(), 0u);
+  }
+
+  static void ObserverBatchElementCheckerElementCallback(
+      std::vector<bool>* res,
+      size_t i,
+      const ClientStatus& status,
+      const std::vector<std::string>& payloads,
+      const base::flat_map<std::string, DomObjectFrameStack>& elms) {
+    (*res)[i] = status.ok();
+  }
+
+  static void ObserverBatchElementCheckerAllDoneCallback(
+      base::OnceClosure on_done,
+      const std::vector<bool>* expected,
+      const std::vector<bool>* actual) {
+    for (size_t i = 0; i < expected->size(); ++i) {
+      EXPECT_EQ(actual->at(i), expected->at(i)) << "condition number " << i;
+    }
+    std::move(on_done).Run();
+  }
+
  protected:
   std::unique_ptr<WebController> web_controller_;
   UserData user_data_;
@@ -3048,6 +3128,110 @@
   WaitForElementRemove(Selector({"#iframeExternal", "#div"}));
 }
 
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+                       ObserverBatchElementCheckerStaticConditions) {
+  RunObserverBatchElementChecker({
+      {Match(Selector({"#button"})), true},        // A visible element.
+      {Match(Selector({"#hidden"})), true},        // A hidden element.
+      {Match(Selector({"#doesnotexist"})), false}  // A nonexistent element.
+  });
+
+  RunObserverBatchElementChecker({{
+      AllOfConditions({
+          Match(Selector({"#button"})),  // A visible element.
+          Match(Selector({"#hidden"})),  // A hidden element.
+      }),
+      true  // Expected to match.
+  }});
+
+  RunObserverBatchElementChecker({{
+      AnyOfConditions({
+          Match(Selector({"#button"})),       // A visible element.
+          Match(Selector({"#doesnotexist"}))  // A nonexistent element.
+      }),
+      true  // Expected to match.
+  }});
+
+  RunObserverBatchElementChecker({{
+      NoneOfConditions({// A nonexistent element.
+                        Match(Selector({"#doesnotexist"})),
+                        // A non-existent element inside an iFrame.
+                        Match(Selector({"#iframe", "#doesnotexists"}))}),
+      true  // Expected to match.
+  }});
+
+  RunObserverBatchElementChecker({{
+      AllOfConditions(
+          {Match(Selector({"#iframe"})),          // An iFrame.
+           Match(Selector({"#iframeExternal"})),  // An OOPIF.
+           Match(Selector(
+               {"#iframe", "#button"})),  // An element in a same-origin iFrame.
+           Match(Selector(
+               {"#iframeExternal", "#button"})),  // An element in an OOPIF.
+           NoneOfConditions(
+               {// A non-existent element in an OOPIF.
+                Match(Selector({"#iframeExternal", "#doesnotexist"})),
+                // A non-existent element in a same-origin iFrame.
+                Match(Selector({"#iframe", "#doesnotexist"}))})}),
+      true  // Expected to match.
+  }});
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+                       ObserverBatchElementCheckerDynamicElements) {
+  RunObserverBatchElementChecker({{
+      // A selector that only matches for ~200ms.
+      Match(Selector({".dynamic.about-2-seconds"})),
+      true  // Expected to match.
+  }});
+
+  RunObserverBatchElementChecker({{
+      // A selector that only matches for ~200ms inside of an iFrame.
+      Match(Selector({"#iframe", ".dynamic.about-2-seconds"})),
+      true  // Expected to match.
+  }});
+
+  RunObserverBatchElementChecker({{
+      // A selector that only matches for ~200ms inside of an external iFrame.
+      Match(Selector({"#iframeExternal", ".dynamic.about-2-seconds"})),
+      true  // Expected to match.
+  }});
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+                       ObserverBatchElementCheckerDifferentFilters) {
+  Selector non_empty_bounding_box = Selector({"#button"});
+  non_empty_bounding_box.proto.add_filters()
+      ->mutable_bounding_box()
+      ->set_require_nonempty(true);
+
+  // Matches exactly one visible element.
+  auto with_inner_text =
+      Selector({"#with_inner_text span"}).MatchingInnerText("hello, world");
+
+  Selector match_css_selector({"label"});
+  match_css_selector.MatchingInnerText("terms and conditions");
+  match_css_selector.proto.add_filters()->mutable_labelled();
+  match_css_selector.proto.add_filters()->set_match_css_selector(
+      "input[type='checkbox']");
+
+  RunObserverBatchElementChecker({{
+      AllOfConditions(
+          {// A visible element.
+           Match(Selector({"#button"}).MustBeVisible()),
+           // An element in a same-origin iFrame.
+           Match(Selector({"#iframe", "#button"}).MustBeVisible()),
+           Match(non_empty_bounding_box), Match(with_inner_text),
+           Match(with_inner_text.MustBeVisible()), Match(match_css_selector),
+           NoneOfConditions(
+               {// A hidden element.
+                Match(Selector({"#hidden"}).MustBeVisible()),
+                // A non-existent element.
+                Match(Selector({"#doesnotexist"}).MustBeVisible())})}),
+      true  // Expected to match.
+  }});
+}
+
 IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectorObserver) {
   base::RunLoop run_loop;
   // Selector ids can be any number as long as unique.
@@ -3101,7 +3285,7 @@
           expected_updates.erase(expected_updates.begin());
         }
         if (expected_updates.empty()) {
-          // Done receiving updates
+          // Done receiving updates.
           observer->GetElementsAndStop(
               {{SelectorObserver::SelectorId(iframe_button_id),
                 /* element_id */ button_element_id}},
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index 3f1dd19..5f41cf15 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -660,9 +660,8 @@
   std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
   if (U_FAILURE(error))
     collator.reset(nullptr);
-  BookmarkNode* mutable_parent = AsMutable(parent);
-  std::sort(mutable_parent->children_.begin(), mutable_parent->children_.end(),
-            SortComparator(collator.get()));
+
+  AsMutable(parent)->SortChildren(SortComparator(collator.get()));
 
   if (store_)
     store_->ScheduleSave();
@@ -690,14 +689,15 @@
     for (size_t i = 0; i < ordered_nodes.size(); ++i)
       order[ordered_nodes[i]] = i;
 
-    std::vector<std::unique_ptr<BookmarkNode>> new_children(
-        ordered_nodes.size());
-    BookmarkNode* mutable_parent = AsMutable(parent);
-    for (auto& child : mutable_parent->children_) {
-      size_t new_location = order[child.get()];
-      new_children[new_location] = std::move(child);
+    std::vector<size_t> new_order(ordered_nodes.size());
+    for (size_t old_index = 0; old_index < parent->children().size();
+         ++old_index) {
+      const BookmarkNode* node = parent->children()[old_index].get();
+      size_t new_index = order[node];
+      new_order[old_index] = new_index;
     }
-    mutable_parent->children_.swap(new_children);
+
+    AsMutable(parent)->ReorderChildren(new_order);
 
     if (store_)
       store_->ScheduleSave();
@@ -811,8 +811,7 @@
       std::make_unique<TypedCountSorter>(client_.get()));
   // Sorting the permanent nodes has to happen on the main thread, so we do it
   // here, after loading completes.
-  std::stable_sort(root_->children_.begin(), root_->children_.end(),
-                   VisibilityComparator(client_.get()));
+  root_->SortChildren(VisibilityComparator(client_.get()));
 
   root_->SetMetaInfoMap(details->model_meta_info_map());
 
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index 6e88c2e..6bc7406 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -23,6 +23,8 @@
 
 android_resources("java_resources") {
   sources = [
+    "java/res/color-night/switch_thumb_tint.xml",
+    "java/res/color-night/switch_track_tint.xml",
     "java/res/color/chip_text_color_secondary.xml",
     "java/res/color/default_icon_color_accent1_tint_list.xml",
     "java/res/color/default_icon_color_dark_tint_list.xml",
@@ -42,7 +44,9 @@
     "java/res/color/progress_bar_bg_color.xml",
     "java/res/color/selection_control_button_tint.xml",
     "java/res/color/switch_thumb_tint.xml",
+    "java/res/color/switch_thumb_tint_incognito.xml",
     "java/res/color/switch_track_tint.xml",
+    "java/res/color/switch_track_tint_incognito.xml",
     "java/res/color/text_highlight_color.xml",
     "java/res/drawable-hdpi/btn_star_filled.png",
     "java/res/drawable-hdpi/ic_chrome.png",
diff --git a/components/browser_ui/styles/android/java/res/color-night/switch_thumb_tint.xml b/components/browser_ui/styles/android/java/res/color-night/switch_thumb_tint.xml
new file mode 100644
index 0000000..7fec1bf
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/color-night/switch_thumb_tint.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@macro/switch_thumb_disabled_color" android:state_enabled="false"/>
+    <item android:color="@macro/default_control_color_active" android:state_checked="true"/>
+    <item android:color="@macro/switch_enabled_unchecked_color_light"/>
+</selector>
diff --git a/components/browser_ui/styles/android/java/res/color-night/switch_track_tint.xml b/components/browser_ui/styles/android/java/res/color-night/switch_track_tint.xml
new file mode 100644
index 0000000..be32c52
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/color-night/switch_track_tint.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:alpha="@dimen/switch_track_disabled_alpha"
+        android:color="@macro/switch_track_disabled_color" android:state_enabled="false"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@macro/default_control_color_active" android:state_checked="true"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@macro/switch_enabled_unchecked_color_light"/>
+</selector>
diff --git a/components/browser_ui/styles/android/java/res/color/switch_thumb_tint.xml b/components/browser_ui/styles/android/java/res/color/switch_thumb_tint.xml
index b36fefe..4b7f25e 100644
--- a/components/browser_ui/styles/android/java/res/color/switch_thumb_tint.xml
+++ b/components/browser_ui/styles/android/java/res/color/switch_thumb_tint.xml
@@ -4,10 +4,7 @@
      found in the LICENSE file.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- This would ideally be colorSwitchThumbNormal at 38% alpha. However, making the thumb
-         translucent causes the track to be visible behind the thumb. This color is not compatible
-         with dynamic colors. -->
-    <item android:color="?attr/colorSwitchThumbDisabledNonDynamic" android:state_enabled="false"/>
-    <item android:color="?attr/colorPrimaryNonDynamic" android:state_checked="true"/>
-    <item android:color="?attr/colorSwitchThumbNormalNonDynamic"/>
+    <item android:color="@macro/switch_thumb_disabled_color" android:state_enabled="false"/>
+    <item android:color="@macro/default_control_color_active" android:state_checked="true"/>
+    <item android:color="@macro/switch_enabled_unchecked_color_dark"/>
 </selector>
diff --git a/components/browser_ui/styles/android/java/res/color/switch_thumb_tint_incognito.xml b/components/browser_ui/styles/android/java/res/color/switch_thumb_tint_incognito.xml
new file mode 100644
index 0000000..bcfef2d
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/color/switch_thumb_tint_incognito.xml
@@ -0,0 +1,10 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/switch_thumb_disabled_color_baseline_dark" android:state_enabled="false"/>
+    <item android:color="@color/default_control_color_active_dark" android:state_checked="true"/>
+    <item android:color="@color/switch_enabled_unchecked_color_baseline_light"/>
+</selector>
diff --git a/components/browser_ui/styles/android/java/res/color/switch_track_tint.xml b/components/browser_ui/styles/android/java/res/color/switch_track_tint.xml
index ad2e6c1..faeaa808 100644
--- a/components/browser_ui/styles/android/java/res/color/switch_track_tint.xml
+++ b/components/browser_ui/styles/android/java/res/color/switch_track_tint.xml
@@ -4,8 +4,10 @@
      found in the LICENSE file.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:alpha="@dimen/default_disabled_alpha"
-        android:color="?attr/colorSwitchTrackNormalNonDynamic" android:state_enabled="false"/>
-    <item android:color="?attr/colorPrimaryContainerNonDynamic" android:state_checked="true"/>
-    <item android:color="?attr/colorSwitchTrackNormalNonDynamic"/>
+    <item android:alpha="@dimen/switch_track_disabled_alpha"
+        android:color="@macro/switch_track_disabled_color" android:state_enabled="false"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@macro/default_control_color_active" android:state_checked="true"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@macro/switch_enabled_unchecked_color_dark"/>
 </selector>
diff --git a/components/browser_ui/styles/android/java/res/color/switch_track_tint_incognito.xml b/components/browser_ui/styles/android/java/res/color/switch_track_tint_incognito.xml
new file mode 100644
index 0000000..86b4d4d3
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/color/switch_track_tint_incognito.xml
@@ -0,0 +1,13 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:alpha="@dimen/switch_track_disabled_alpha"
+        android:color="@color/switch_track_disabled_color_baseline_light" android:state_enabled="false"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@color/default_control_color_active_dark" android:state_checked="true"/>
+    <item android:alpha="@dimen/switch_track_enabled_alpha"
+        android:color="@color/switch_enabled_unchecked_color_baseline_light"/>
+</selector>
diff --git a/components/browser_ui/styles/android/java/res/values-night/colors.xml b/components/browser_ui/styles/android/java/res/values-night/colors.xml
index 5c5c4bbd2..d212f1a 100644
--- a/components/browser_ui/styles/android/java/res/values-night/colors.xml
+++ b/components/browser_ui/styles/android/java/res/values-night/colors.xml
@@ -7,4 +7,6 @@
     <!-- Control colors for toggles, checkboxes, radio buttons, list item highlight, and accent.
          Note that these should NOT be used for icon tint. -->
     <color name="control_highlight_color">@color/default_control_color_highlight_dark</color>
+    <color name="switch_track_disabled_color_baseline">@color/switch_track_disabled_color_baseline_light</color>
+    <color name="switch_thumb_disabled_color_baseline">@color/switch_thumb_disabled_color_baseline_dark</color>
 </resources>
diff --git a/components/browser_ui/styles/android/java/res/values-night/styles.xml b/components/browser_ui/styles/android/java/res/values-night/styles.xml
index 14aadea..608357d 100644
--- a/components/browser_ui/styles/android/java/res/values-night/styles.xml
+++ b/components/browser_ui/styles/android/java/res/values-night/styles.xml
@@ -21,17 +21,5 @@
         <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
         <item name="elevationOverlayColor">@color/baseline_neutral_200</item>
         <item name="elevationOverlayEnabled">true</item>
-
-        <item name="colorSwitchThumbNormal">?attr/colorOnSurface</item>
-        <item name="colorSwitchThumbDisabled">?attr/colorFixedOnSurfaceAlpha38OverSurface</item>
-        <item name="colorSwitchTrackNormal">?attr/colorOutline</item>
-        <item name="colorFixedOnSurfaceAlpha38OverSurface">@color/baseline_neutral_900_with_neutral_100_alpha_38</item>
-
-        <!-- Splintered SwitchMaterial roles to roll back dynamic colors. -->
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_200</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_100</item>
-        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_900_with_neutral_100_alpha_38</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_400</item>
     </style>
 </resources>
diff --git a/components/browser_ui/styles/android/java/res/values-night/themes.xml b/components/browser_ui/styles/android/java/res/values-night/themes.xml
index 30657b4..c171db4 100644
--- a/components/browser_ui/styles/android/java/res/values-night/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values-night/themes.xml
@@ -22,19 +22,5 @@
 
         <!-- Elevation overlays -->
         <item name="elevationOverlayColor">@color/baseline_neutral_200</item>
-
-        <!-- Custom roles -->
-        <!-- Dynamic roles -->
-        <item name="colorSwitchThumbNormal">?attr/colorOnSurface</item>
-        <item name="colorSwitchThumbDisabled">?attr/colorFixedOnSurfaceAlpha38OverSurface</item>
-        <item name="colorSwitchTrackNormal">?attr/colorOutline</item>
-        <!-- Non-dynamic roles for switches -->
-        <item name="colorFixedOnSurfaceAlpha38OverSurface">@color/baseline_neutral_900_with_neutral_100_alpha_38</item>
-        <!-- Splintered SwitchMaterial roles to roll back dynamic colors. -->
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_200</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_100</item>
-        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_900_with_neutral_100_alpha_38</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_400</item>
     </style>
 </resources>
diff --git a/components/browser_ui/styles/android/java/res/values/colors.xml b/components/browser_ui/styles/android/java/res/values/colors.xml
index 4b412273..2cd5ba0 100644
--- a/components/browser_ui/styles/android/java/res/values/colors.xml
+++ b/components/browser_ui/styles/android/java/res/values/colors.xml
@@ -22,6 +22,12 @@
     <color name="control_highlight_color" tools:ignore="UnusedResources">
         @color/default_control_color_highlight_light
     </color>
+    <color name="switch_enabled_unchecked_color_baseline_dark" tools:ignore="UnusedResources">@color/baseline_neutral_variant_500</color>
+    <color name="switch_enabled_unchecked_color_baseline_light">@color/baseline_neutral_100</color>
+    <color name="switch_track_disabled_color_baseline" tools:ignore="UnusedResources">@color/baseline_neutral_900</color>
+    <color name="switch_track_disabled_color_baseline_light">@color/baseline_neutral_100</color>
+    <color name="switch_thumb_disabled_color_baseline" tools:ignore="UnusedResources">@color/baseline_neutral_50</color>
+    <color name="switch_thumb_disabled_color_baseline_dark">@color/baseline_neutral_800</color>
 
     <!-- Favicons -->
     <color name="default_favicon_background_color">@color/default_control_color_normal_light</color>
diff --git a/components/browser_ui/styles/android/java/res/values/dimens.xml b/components/browser_ui/styles/android/java/res/values/dimens.xml
index c55ea4d..0fc5ebd 100644
--- a/components/browser_ui/styles/android/java/res/values/dimens.xml
+++ b/components/browser_ui/styles/android/java/res/values/dimens.xml
@@ -18,8 +18,11 @@
     <dimen name="toolbar_shadow_height">8dp</dimen>
     <dimen name="toolbar_hairline_height">1dp</dimen>
 
+    <!-- Alpha values for various things -->
     <item name="text_highlight_alpha" format="float" type="dimen">0.2</item>
     <item name="progress_bar_bg_alpha" format="float" type="dimen">0.2</item>
+    <item name="switch_track_disabled_alpha" format="float" type="dimen">0.12</item>
+    <item name="switch_track_enabled_alpha" format="float" type="dimen">0.3</item>
 
     <!-- Surface color elevations -->
     <dimen name="dialog_bg_color_elev">@dimen/default_elevation_0</dimen>
diff --git a/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml b/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml
index e7457af9..2ef5e261 100644
--- a/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml
+++ b/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml
@@ -7,16 +7,25 @@
     <!--
     <macro name="default_bg_color">?attr/colorSurface</macro>
     <macro name="default_bg_color_elev_0">?attr/colorSurface</macro>
+
     <macro name="default_control_color_active">?attr/colorPrimary</macro>
     <macro name="default_control_color_normal">?attr/colorOnSurfaceVariant</macro>
+
     <macro name="default_icon_color">?attr/colorOnSurface</macro>
     <macro name="default_icon_color_accent1">?attr/colorPrimary</macro>
     <macro name="default_icon_color_inverse">?attr/colorOnSurfaceInverse</macro>
     <macro name="default_icon_color_on_accent1">?attr/colorOnPrimary</macro>
     <macro name="default_icon_color_secondary">?attr/colorOnSurfaceVariant</macro>
+
     <macro name="default_text_color">?attr/colorOnSurface</macro>
     <macro name="default_text_color_accent1">?attr/colorPrimary</macro>
     <macro name="default_text_color_on_accent1">?attr/colorOnPrimary</macro>
+
+    <macro name="switch_enabled_unchecked_color_dark">?attr/colorOutline</macro>
+    <macro name="switch_enabled_unchecked_color_light">?attr/colorOnSurface</macro>
+    <macro name="switch_track_disabled_color">?attr/colorOnSurface</macro>
+    <macro name="switch_thumb_disabled_color">?attr/colorOnSurfaceInverse</macro>
+
     <macro name="hairline_stroke_color">?attr/colorOutline</macro>
     <macro name="divider_line_bg_color">?attr/colorSurfaceVariant</macro>
     -->
@@ -24,16 +33,25 @@
     <!-- Temporarily holdback @color version. Use for checkin. -->
     <macro name="default_bg_color">@color/default_bg_color_baseline</macro>
     <macro name="default_bg_color_elev_0">@color/default_bg_color_elev_0_baseline</macro>
+
     <macro name="default_control_color_active">@color/default_control_color_active_baseline</macro>
     <macro name="default_control_color_normal">@color/default_control_color_normal_baseline</macro>
+
     <macro name="default_icon_color">@color/default_icon_color_baseline</macro>
     <macro name="default_icon_color_accent1">@color/default_icon_color_accent1_baseline</macro>
     <macro name="default_icon_color_inverse">@color/default_icon_color_inverse_baseline</macro>
     <macro name="default_icon_color_on_accent1">@color/default_icon_color_on_accent1_baseline</macro>
     <macro name="default_icon_color_secondary">@color/default_icon_color_secondary_baseline</macro>
+
     <macro name="default_text_color">@color/default_text_color_baseline</macro>
     <macro name="default_text_color_accent1">@color/default_text_color_blue_baseline</macro>
     <macro name="default_text_color_on_accent1">@color/default_text_color_on_accent1_baseline</macro>
+
+    <macro name="switch_enabled_unchecked_color_dark">@color/switch_enabled_unchecked_color_baseline_dark</macro>
+    <macro name="switch_enabled_unchecked_color_light">@color/switch_enabled_unchecked_color_baseline_light</macro>
+    <macro name="switch_track_disabled_color">@color/switch_track_disabled_color_baseline</macro>
+    <macro name="switch_thumb_disabled_color">@color/switch_thumb_disabled_color_baseline</macro>
+
     <macro name="hairline_stroke_color">@color/hairline_stroke_color_baseline</macro>
     <macro name="divider_line_bg_color">@color/divider_line_bg_color_baseline</macro>
 
diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml
index b6fd206..231af0d 100644
--- a/components/browser_ui/styles/android/java/res/values/styles.xml
+++ b/components/browser_ui/styles/android/java/res/values/styles.xml
@@ -85,6 +85,10 @@
         <item name="trackTint">@color/switch_track_tint</item>
         <item name="trackTintMode">src_in</item>
     </style>
+    <style name="Widget.BrowserUI.Switch.Incognito" parent="Widget.MaterialComponents.CompoundButton.Switch">
+        <item name="thumbTint">@color/switch_thumb_tint_incognito</item>
+        <item name="trackTint">@color/switch_track_tint_incognito</item>
+    </style>
     <!-- TextInputLayout style -->
     <style name="Widget.BrowserUI.TextInputLayout" parent="Widget.Design.TextInputLayout">
         <item name="errorTextAppearance">@style/TextAppearance.ErrorCaption</item>
@@ -181,12 +185,12 @@
     <!-- TODO(sinansahin): See if we can pass the baseline dark theme to the incognito NTP instead.
          -->
     <style name="SwitchMaterialDark">
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_200</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_100</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_400</item>
+        <!-- TODO(sinansahin): When we migrate the switch style to Material3, we should override
+             colorOnSurface and colorPrimary instead of these attributes because they are what
+             m3_selection_control_ripple_color_selector uses. -->
+        <item name="colorControlActivated">@color/default_control_color_active_dark</item>
         <item name="colorControlHighlight">@color/default_control_color_highlight_dark</item>
-        <item name="switchStyle">@style/Widget.BrowserUI.Switch</item>
+        <item name="switchStyle">@style/Widget.BrowserUI.Switch.Incognito</item>
     </style>
 
     <!-- Styling for an app menu item row. -->
diff --git a/components/browser_ui/styles/android/java/res/values/themes.xml b/components/browser_ui/styles/android/java/res/values/themes.xml
index 06f7517..672eb34e 100644
--- a/components/browser_ui/styles/android/java/res/values/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values/themes.xml
@@ -42,18 +42,6 @@
         <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
         <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
 
-        <!-- Custom roles -->
-        <!-- Dynamic roles -->
-        <item name="colorSwitchThumbNormal">?attr/colorSurface</item>
-        <item name="colorSwitchThumbDisabled">?attr/colorSurface</item>
-        <item name="colorSwitchTrackNormal">?attr/colorSurfaceVariant</item>
-        <!-- Non-dynamic roles for switches -->
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_600</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_100</item>
-
         <!-- Custom semantic names -->
         <!-- Supports dynamic colors now. -->
         <item name="default_bg_color_dynamic">?attr/colorSurface</item>
@@ -100,18 +88,6 @@
         <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
         <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
 
-        <!-- Custom roles -->
-        <!-- Dynamic roles -->
-        <item name="colorSwitchThumbNormal">?attr/colorSurface</item>
-        <item name="colorSwitchThumbDisabled">?attr/colorSurface</item>
-        <item name="colorSwitchTrackNormal">?attr/colorSurfaceVariant</item>
-        <!-- Non-dynamic roles for switches -->
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_600</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_100</item>
-
         <!-- Custom semantic names -->
         <!-- Supports dynamic colors now. -->
         <item name="default_bg_color_dynamic">?attr/colorSurface</item>
@@ -183,17 +159,6 @@
         <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
         <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
         <item name="elevationOverlayEnabled">true</item>
-
-        <item name="colorSwitchThumbNormal">?attr/colorSurface</item>
-        <item name="colorSwitchThumbDisabled">?attr/colorSurface</item>
-        <item name="colorSwitchTrackNormal">?attr/colorSurfaceVariant</item>
-
-        <!-- Non-dynamic roles for switches. -->
-        <item name="colorPrimaryNonDynamic">@color/baseline_primary_600</item>
-        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
-        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_0</item>
-        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_100</item>
     </style>
     <style name="ColorOverlay.BrowserUi.DayNight" parent="ColorOverlay.Ui">
         <item name="android:textColorPrimary">@color/default_text_color_list</item>
diff --git a/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java b/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java
index a5f3dfd..a8a710f 100644
--- a/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java
+++ b/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java
@@ -136,6 +136,30 @@
     }
 
     @Override
+    public void setPriorityNotification(String feature) {
+        TrackerImplJni.get().setPriorityNotification(mNativePtr, TrackerImpl.this, feature);
+    }
+
+    @Override
+    @Nullable
+    public String getPendingPriorityNotification() {
+        return TrackerImplJni.get().getPendingPriorityNotification(mNativePtr, TrackerImpl.this);
+    }
+
+    @Override
+    public void registerPriorityNotificationHandler(
+            String feature, Runnable priorityNotificationHandler) {
+        TrackerImplJni.get().registerPriorityNotificationHandler(
+                mNativePtr, TrackerImpl.this, feature, priorityNotificationHandler);
+    }
+
+    @Override
+    public void unregisterPriorityNotificationHandler(String feature) {
+        TrackerImplJni.get().unregisterPriorityNotificationHandler(
+                mNativePtr, TrackerImpl.this, feature);
+    }
+
+    @Override
     public boolean isInitialized() {
         assert mNativePtr != 0;
         return TrackerImplJni.get().isInitialized(mNativePtr, TrackerImpl.this);
@@ -181,6 +205,13 @@
                 int snoozeAction);
         DisplayLockHandleAndroid acquireDisplayLock(
                 long nativeTrackerImplAndroid, TrackerImpl caller);
+        void setPriorityNotification(
+                long nativeTrackerImplAndroid, TrackerImpl caller, String feature);
+        String getPendingPriorityNotification(long nativeTrackerImplAndroid, TrackerImpl caller);
+        void registerPriorityNotificationHandler(long nativeTrackerImplAndroid, TrackerImpl caller,
+                String feature, Runnable priorityNotificationHandler);
+        void unregisterPriorityNotificationHandler(
+                long nativeTrackerImplAndroid, TrackerImpl caller, String feature);
         boolean isInitialized(long nativeTrackerImplAndroid, TrackerImpl caller);
         void addOnInitializedCallback(
                 long nativeTrackerImplAndroid, TrackerImpl caller, Callback<Boolean> callback);
diff --git a/components/feature_engagement/internal/android/tracker_impl_android.cc b/components/feature_engagement/internal/android/tracker_impl_android.cc
index 175cff0..4a591df 100644
--- a/components/feature_engagement/internal/android/tracker_impl_android.cc
+++ b/components/feature_engagement/internal/android/tracker_impl_android.cc
@@ -18,6 +18,7 @@
 #include "components/feature_engagement/internal/jni_headers/TrackerImpl_jni.h"
 #include "components/feature_engagement/public/feature_list.h"
 #include "components/feature_engagement/public/tracker.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 
@@ -185,6 +186,51 @@
   return lock_handle_android.release()->GetJavaObject();
 }
 
+void TrackerImplAndroid::SetPriorityNotification(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& jobj,
+    const base::android::JavaParamRef<jstring>& jfeature) {
+  std::string feature = ConvertJavaStringToUTF8(env, jfeature);
+  DCHECK(features_.find(feature) != features_.end());
+
+  return tracker_->SetPriorityNotification(*features_[feature]);
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+TrackerImplAndroid::GetPendingPriorityNotification(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& jobj) {
+  auto notification = tracker_->GetPendingPriorityNotification();
+  std::string pending_notification_string =
+      notification.value_or(std::string());
+  return base::android::ConvertUTF8ToJavaString(env,
+                                                pending_notification_string);
+}
+
+void TrackerImplAndroid::RegisterPriorityNotificationHandler(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& jobj,
+    const base::android::JavaParamRef<jstring>& jfeature,
+    const base::android::JavaRef<jobject>& jrunnable) {
+  std::string feature = ConvertJavaStringToUTF8(env, jfeature);
+  DCHECK(features_.find(feature) != features_.end());
+
+  return tracker_->RegisterPriorityNotificationHandler(
+      *features_[feature],
+      base::BindOnce(&base::android::RunRunnableAndroid,
+                     base::android::ScopedJavaGlobalRef<jobject>(jrunnable)));
+}
+
+void TrackerImplAndroid::UnregisterPriorityNotificationHandler(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& jobj,
+    const base::android::JavaParamRef<jstring>& jfeature) {
+  std::string feature = ConvertJavaStringToUTF8(env, jfeature);
+  DCHECK(features_.find(feature) != features_.end());
+
+  return tracker_->UnregisterPriorityNotificationHandler(*features_[feature]);
+}
+
 bool TrackerImplAndroid::IsInitialized(
     JNIEnv* env,
     const base::android::JavaRef<jobject>& jobj) {
diff --git a/components/feature_engagement/internal/android/tracker_impl_android.h b/components/feature_engagement/internal/android/tracker_impl_android.h
index e877697..9a686747 100644
--- a/components/feature_engagement/internal/android/tracker_impl_android.h
+++ b/components/feature_engagement/internal/android/tracker_impl_android.h
@@ -111,6 +111,22 @@
   virtual base::android::ScopedJavaLocalRef<jobject> AcquireDisplayLock(
       JNIEnv* env,
       const base::android::JavaRef<jobject>& jobj);
+  virtual void SetPriorityNotification(
+      JNIEnv* env,
+      const base::android::JavaRef<jobject>& jobj,
+      const base::android::JavaParamRef<jstring>& jfeature);
+  virtual base::android::ScopedJavaLocalRef<jstring>
+  GetPendingPriorityNotification(JNIEnv* env,
+                                 const base::android::JavaRef<jobject>& jobj);
+  virtual void RegisterPriorityNotificationHandler(
+      JNIEnv* env,
+      const base::android::JavaRef<jobject>& jobj,
+      const base::android::JavaParamRef<jstring>& jfeature,
+      const base::android::JavaRef<jobject>& jcallback);
+  virtual void UnregisterPriorityNotificationHandler(
+      JNIEnv* env,
+      const base::android::JavaRef<jobject>& jobj,
+      const base::android::JavaParamRef<jstring>& jfeature);
   virtual bool IsInitialized(JNIEnv* env,
                              const base::android::JavaRef<jobject>& jobj);
   virtual void AddOnInitializedCallback(
diff --git a/components/feature_engagement/internal/condition_validator.cc b/components/feature_engagement/internal/condition_validator.cc
index 155cbdf..2bada26 100644
--- a/components/feature_engagement/internal/condition_validator.cc
+++ b/components/feature_engagement/internal/condition_validator.cc
@@ -21,6 +21,7 @@
       availability_ok(initial_values),
       display_lock_ok(initial_values),
       snooze_expiration_ok(initial_values),
+      priority_notification_ok(initial_values),
       should_show_snooze(initial_values) {}
 
 ConditionValidator::Result::Result(const Result& other) = default;
@@ -32,7 +33,7 @@
   return event_model_ready_ok && currently_showing_ok && feature_enabled_ok &&
          config_ok && used_ok && trigger_ok && preconditions_ok &&
          session_rate_ok && availability_model_ready_ok && availability_ok &&
-         display_lock_ok && snooze_expiration_ok;
+         display_lock_ok && snooze_expiration_ok && priority_notification_ok;
 }
 
 std::ostream& operator<<(std::ostream& os,
@@ -50,6 +51,7 @@
             << ", availability_ok=" << result.availability_ok
             << ", display_lock_ok=" << result.display_lock_ok
             << ", snooze_expiration_ok=" << result.snooze_expiration_ok
+            << ", priority_notification_ok=" << result.priority_notification_ok
             << ", should_show_snooze=" << result.should_show_snooze << " }";
 }
 
diff --git a/components/feature_engagement/internal/condition_validator.h b/components/feature_engagement/internal/condition_validator.h
index edd4700..facd64d 100644
--- a/components/feature_engagement/internal/condition_validator.h
+++ b/components/feature_engagement/internal/condition_validator.h
@@ -13,6 +13,7 @@
 
 #include "components/feature_engagement/public/configuration.h"
 #include "components/feature_engagement/public/feature_list.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 struct Feature;
@@ -73,6 +74,10 @@
     // Whether the current snooze timer has expired.
     bool snooze_expiration_ok;
 
+    // Whether the given feature is a priority notification, or there are no
+    // other priority notifications.
+    bool priority_notification_ok;
+
     // Whether the snooze option should be shown.
     // This value is excluded from the NoErrors() check.
     bool should_show_snooze;
@@ -106,6 +111,14 @@
   // Must be called to notify that the |feature| is no longer showing.
   virtual void NotifyDismissed(const base::Feature& feature) = 0;
 
+  // Called to notify that we have a priority notification to be shown next. All
+  // other IPHs will be blocked until then.
+  virtual void SetPriorityNotification(
+      const absl::optional<std::string>& feature) = 0;
+
+  // Called to get if there is a pending priority notification to be shown next.
+  virtual absl::optional<std::string> GetPendingPriorityNotification() = 0;
+
  protected:
   ConditionValidator() = default;
 };
diff --git a/components/feature_engagement/internal/condition_validator_unittest.cc b/components/feature_engagement/internal/condition_validator_unittest.cc
index 860d396..70c734aea 100644
--- a/components/feature_engagement/internal/condition_validator_unittest.cc
+++ b/components/feature_engagement/internal/condition_validator_unittest.cc
@@ -82,6 +82,12 @@
   EXPECT_FALSE(result.NoErrors());
 }
 
+TEST(ConditionValidatorResultTest, TestPriorityNotificationFailed) {
+  ConditionValidator::Result result(true);
+  result.priority_notification_ok = false;
+  EXPECT_FALSE(result.NoErrors());
+}
+
 TEST(ConditionValidatorResultTest, TestMultipleErrors) {
   ConditionValidator::Result result(true);
   result.preconditions_ok = false;
diff --git a/components/feature_engagement/internal/feature_config_condition_validator.cc b/components/feature_engagement/internal/feature_config_condition_validator.cc
index 1144945..7c5ae7f 100644
--- a/components/feature_engagement/internal/feature_config_condition_validator.cc
+++ b/components/feature_engagement/internal/feature_config_condition_validator.cc
@@ -18,6 +18,7 @@
 #include "components/feature_engagement/internal/proto/feature_event.pb.h"
 #include "components/feature_engagement/public/configuration.h"
 #include "components/feature_engagement/public/feature_list.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 
@@ -63,6 +64,10 @@
       (event_model.GetLastSnoozeTimestamp(config.trigger.name) <
        base::Time::Now() - base::Days(config.snooze_params.snooze_interval));
 
+  result.priority_notification_ok =
+      !pending_priority_notification_.has_value() ||
+      pending_priority_notification_.value() == feature.name;
+
   result.should_show_snooze =
       result.snooze_expiration_ok &&
       event_model.GetSnoozeCount(config.trigger.name, config.trigger.window,
@@ -115,6 +120,17 @@
   return event_config.comparator.MeetsCriteria(event_count);
 }
 
+void FeatureConfigConditionValidator::SetPriorityNotification(
+    const absl::optional<std::string>& feature) {
+  DCHECK(!pending_priority_notification_.has_value() || !feature.has_value());
+  pending_priority_notification_ = feature;
+}
+
+absl::optional<std::string>
+FeatureConfigConditionValidator::GetPendingPriorityNotification() {
+  return pending_priority_notification_;
+}
+
 bool FeatureConfigConditionValidator::AvailabilityMeetsConditions(
     const base::Feature& feature,
     Comparator comparator,
diff --git a/components/feature_engagement/internal/feature_config_condition_validator.h b/components/feature_engagement/internal/feature_config_condition_validator.h
index 4e1583c..37bf99d 100644
--- a/components/feature_engagement/internal/feature_config_condition_validator.h
+++ b/components/feature_engagement/internal/feature_config_condition_validator.h
@@ -10,6 +10,7 @@
 #include <set>
 
 #include "components/feature_engagement/internal/condition_validator.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 class AvailabilityModel;
@@ -43,6 +44,9 @@
       const FeatureConfig& config,
       const std::vector<std::string>& all_feature_names) override;
   void NotifyDismissed(const base::Feature& feature) override;
+  void SetPriorityNotification(
+      const absl::optional<std::string>& feature) override;
+  absl::optional<std::string> GetPendingPriorityNotification() override;
 
  private:
   bool EventConfigMeetsConditions(const EventConfig& event_config,
@@ -69,6 +73,9 @@
   // By default, all features impact each other, but some features override this
   // through the use of |session_rate_impact|.
   std::map<std::string, uint32_t> times_shown_for_feature_;
+
+  // Pending priority notification to be shown if any.
+  absl::optional<std::string> pending_priority_notification_;
 };
 
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc b/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc
index ca69e34..bb10b17 100644
--- a/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc
+++ b/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc
@@ -440,6 +440,50 @@
   EXPECT_FALSE(result.preconditions_ok);
 }
 
+TEST_F(FeatureConfigConditionValidatorTest, PriorityNotification) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {kFeatureConfigTestFeatureFoo, kFeatureConfigTestFeatureBar}, {});
+  std::vector<std::string> all_feature_names = {
+      kFeatureConfigTestFeatureFoo.name, kFeatureConfigTestFeatureBar.name};
+
+  FeatureConfig foo_config = GetAcceptingFeatureConfig();
+  FeatureConfig bar_config = GetAcceptingFeatureConfig();
+
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureFoo, foo_config)
+          .NoErrors());
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureBar, bar_config)
+          .NoErrors());
+
+  validator_.SetPriorityNotification(kFeatureConfigTestFeatureFoo.name);
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureFoo, foo_config)
+          .NoErrors());
+  ConditionValidator::Result result =
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureBar, bar_config);
+  EXPECT_FALSE(result.NoErrors());
+  EXPECT_FALSE(result.priority_notification_ok);
+
+  validator_.SetPriorityNotification(absl::nullopt);
+  validator_.SetPriorityNotification(kFeatureConfigTestFeatureBar.name);
+  EXPECT_FALSE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureFoo, foo_config)
+          .NoErrors());
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureBar, bar_config)
+          .NoErrors());
+
+  validator_.SetPriorityNotification(absl::nullopt);
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureFoo, foo_config)
+          .NoErrors());
+  EXPECT_TRUE(
+      GetResultForDayZeroForFeature(kFeatureConfigTestFeatureBar, bar_config)
+          .NoErrors());
+}
+
 TEST_F(FeatureConfigConditionValidatorTest, SessionRate) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
diff --git a/components/feature_engagement/internal/never_condition_validator.cc b/components/feature_engagement/internal/never_condition_validator.cc
index 5108c06a..7fbfa601 100644
--- a/components/feature_engagement/internal/never_condition_validator.cc
+++ b/components/feature_engagement/internal/never_condition_validator.cc
@@ -4,6 +4,8 @@
 
 #include "components/feature_engagement/internal/never_condition_validator.h"
 
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
 namespace feature_engagement {
 
 NeverConditionValidator::NeverConditionValidator() = default;
@@ -28,4 +30,12 @@
 
 void NeverConditionValidator::NotifyDismissed(const base::Feature& feature) {}
 
+void NeverConditionValidator::SetPriorityNotification(
+    const absl::optional<std::string>& feature) {}
+
+absl::optional<std::string>
+NeverConditionValidator::GetPendingPriorityNotification() {
+  return absl::nullopt;
+}
+
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/never_condition_validator.h b/components/feature_engagement/internal/never_condition_validator.h
index b7ecdba0..b5f6c15 100644
--- a/components/feature_engagement/internal/never_condition_validator.h
+++ b/components/feature_engagement/internal/never_condition_validator.h
@@ -7,6 +7,7 @@
 
 #include "components/feature_engagement/internal/condition_validator.h"
 #include "components/feature_engagement/public/feature_list.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 struct Feature;
@@ -42,6 +43,9 @@
       const FeatureConfig& config,
       const std::vector<std::string>& all_feature_names) override;
   void NotifyDismissed(const base::Feature& feature) override;
+  void SetPriorityNotification(
+      const absl::optional<std::string>& feature) override;
+  absl::optional<std::string> GetPendingPriorityNotification() override;
 };
 
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/never_condition_validator_unittest.cc b/components/feature_engagement/internal/never_condition_validator_unittest.cc
index fcaea750..e3da1cb 100644
--- a/components/feature_engagement/internal/never_condition_validator_unittest.cc
+++ b/components/feature_engagement/internal/never_condition_validator_unittest.cc
@@ -98,6 +98,7 @@
                                     event_model_, availability_model_,
                                     display_lock_controller_, nullptr, 0u)
                    .NoErrors());
+  EXPECT_FALSE(validator_.GetPendingPriorityNotification().has_value());
 }
 
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/once_condition_validator.cc b/components/feature_engagement/internal/once_condition_validator.cc
index 975d859..629329d 100644
--- a/components/feature_engagement/internal/once_condition_validator.cc
+++ b/components/feature_engagement/internal/once_condition_validator.cc
@@ -6,6 +6,7 @@
 
 #include "components/feature_engagement/internal/event_model.h"
 #include "components/feature_engagement/public/configuration.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 
@@ -38,6 +39,10 @@
       (event_model.GetLastSnoozeTimestamp(config.trigger.name) <
        base::Time::Now() - base::Days(config.snooze_params.snooze_interval));
 
+  result.priority_notification_ok =
+      !pending_priority_notification_.has_value() ||
+      pending_priority_notification_.value() == feature.name;
+
   result.should_show_snooze =
       result.snooze_expiration_ok &&
       event_model.GetSnoozeCount(config.trigger.name, config.trigger.window,
@@ -61,4 +66,14 @@
   currently_showing_feature_.clear();
 }
 
+void OnceConditionValidator::SetPriorityNotification(
+    const absl::optional<std::string>& feature) {
+  pending_priority_notification_ = feature;
+}
+
+absl::optional<std::string>
+OnceConditionValidator::GetPendingPriorityNotification() {
+  return pending_priority_notification_;
+}
+
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/once_condition_validator.h b/components/feature_engagement/internal/once_condition_validator.h
index 726493e4..aa8791f 100644
--- a/components/feature_engagement/internal/once_condition_validator.h
+++ b/components/feature_engagement/internal/once_condition_validator.h
@@ -9,6 +9,7 @@
 
 #include "components/feature_engagement/internal/condition_validator.h"
 #include "components/feature_engagement/public/feature_list.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 struct Feature;
@@ -55,6 +56,9 @@
       const FeatureConfig& config,
       const std::vector<std::string>& all_feature_names) override;
   void NotifyDismissed(const base::Feature& feature) override;
+  void SetPriorityNotification(
+      const absl::optional<std::string>& feature) override;
+  absl::optional<std::string> GetPendingPriorityNotification() override;
 
  private:
   // Contains all features that have met conditions within the current session.
@@ -63,6 +67,9 @@
   // Which feature that is currently being shown, or nullptr if nothing is
   // currently showing.
   std::string currently_showing_feature_;
+
+  // Pending priority notification to be shown if any.
+  absl::optional<std::string> pending_priority_notification_;
 };
 
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/internal/once_condition_validator_unittest.cc b/components/feature_engagement/internal/once_condition_validator_unittest.cc
index c7e8a65..64305c3 100644
--- a/components/feature_engagement/internal/once_condition_validator_unittest.cc
+++ b/components/feature_engagement/internal/once_condition_validator_unittest.cc
@@ -176,6 +176,22 @@
                   .NoErrors());
 }
 
+TEST_F(OnceConditionValidatorTest, PriorityNotificationBlocksOtherIPHs) {
+  validator_.SetPriorityNotification("test_bar");
+  ConditionValidator::Result result = validator_.MeetsConditions(
+      kOnceTestFeatureFoo, kValidFeatureConfig, event_model_,
+      availability_model_, display_lock_controller_, nullptr, 0u);
+  EXPECT_FALSE(result.NoErrors());
+  EXPECT_FALSE(result.priority_notification_ok);
+
+  validator_.SetPriorityNotification(absl::nullopt);
+  EXPECT_TRUE(validator_
+                  .MeetsConditions(kOnceTestFeatureFoo, kValidFeatureConfig,
+                                   event_model_, availability_model_,
+                                   display_lock_controller_, nullptr, 0u)
+                  .NoErrors());
+}
+
 TEST_F(OnceConditionValidatorTest, DoNotTriggerForInvalidConfig) {
   ConditionValidator::Result result = validator_.MeetsConditions(
       kOnceTestFeatureFoo, kInvalidFeatureConfig, event_model_,
diff --git a/components/feature_engagement/internal/tracker_impl.cc b/components/feature_engagement/internal/tracker_impl.cc
index 885df49..66842b9c 100644
--- a/components/feature_engagement/internal/tracker_impl.cc
+++ b/components/feature_engagement/internal/tracker_impl.cc
@@ -36,6 +36,7 @@
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/feature_engagement/public/feature_list.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 
@@ -296,6 +297,46 @@
   return display_lock_controller_->AcquireDisplayLock();
 }
 
+void TrackerImpl::SetPriorityNotification(const base::Feature& feature) {
+  // If the handler hasn't been registered.
+  auto iter = priority_notification_handlers_.find(feature.name);
+  if (iter == priority_notification_handlers_.end()) {
+    condition_validator_->SetPriorityNotification(feature.name);
+    return;
+  }
+
+  // We already have a handler. Serve the request and remove the handler.
+  condition_validator_->SetPriorityNotification(absl::nullopt);
+  std::move(iter->second).Run();
+  priority_notification_handlers_.erase(feature.name);
+}
+
+absl::optional<std::string> TrackerImpl::GetPendingPriorityNotification() {
+  return condition_validator_->GetPendingPriorityNotification();
+}
+
+void TrackerImpl::RegisterPriorityNotificationHandler(
+    const base::Feature& feature,
+    base::OnceClosure callback) {
+  // If we already have a pending notification, handle it right away.
+  auto pending_priority_notification =
+      condition_validator_->GetPendingPriorityNotification();
+  if (pending_priority_notification.has_value() &&
+      pending_priority_notification.value() == feature.name) {
+    std::move(callback).Run();
+    condition_validator_->SetPriorityNotification(absl::nullopt);
+    return;
+  }
+
+  // We don't have the notification yet. Cache the handler.
+  priority_notification_handlers_.emplace(feature.name, std::move(callback));
+}
+
+void TrackerImpl::UnregisterPriorityNotificationHandler(
+    const base::Feature& feature) {
+  priority_notification_handlers_.erase(feature.name);
+}
+
 bool TrackerImpl::IsInitialized() const {
   return event_model_->IsReady() && availability_model_->IsReady();
 }
diff --git a/components/feature_engagement/internal/tracker_impl.h b/components/feature_engagement/internal/tracker_impl.h
index 0c1621bd..21580b3 100644
--- a/components/feature_engagement/internal/tracker_impl.h
+++ b/components/feature_engagement/internal/tracker_impl.h
@@ -12,6 +12,7 @@
 #include "base/feature_list.h"
 #include "base/memory/weak_ptr.h"
 #include "components/feature_engagement/public/tracker.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 class AvailabilityModel;
@@ -53,6 +54,12 @@
   std::unique_ptr<DisplayLockHandle> AcquireDisplayLock() override;
   bool IsInitialized() const override;
   void AddOnInitializedCallback(OnInitializedCallback callback) override;
+  void SetPriorityNotification(const base::Feature& feature) override;
+  absl::optional<std::string> GetPendingPriorityNotification() override;
+  void RegisterPriorityNotificationHandler(const base::Feature& feature,
+                                           base::OnceClosure callback) override;
+  void UnregisterPriorityNotificationHandler(
+      const base::Feature& feature) override;
 
  private:
   // Invoked by the EventModel when it has been initialized.
@@ -102,6 +109,9 @@
   // is cleared after the initialization has happened.
   std::vector<OnInitializedCallback> on_initialized_callbacks_;
 
+  // Registered priority notification handlers for various features.
+  std::map<std::string, base::OnceClosure> priority_notification_handlers_;
+
   base::WeakPtrFactory<TrackerImpl> weak_ptr_factory_{this};
 };
 
diff --git a/components/feature_engagement/internal/tracker_impl_unittest.cc b/components/feature_engagement/internal/tracker_impl_unittest.cc
index ad76fee..0b407e6 100644
--- a/components/feature_engagement/internal/tracker_impl_unittest.cc
+++ b/components/feature_engagement/internal/tracker_impl_unittest.cc
@@ -9,12 +9,14 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback_forward.h"
 #include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/task_environment.h"
@@ -646,6 +648,85 @@
   EXPECT_FALSE(callback.success());
 }
 
+TEST_F(TrackerImplTest, TestSetPriorityNotificationBeforeRegistration) {
+  // Ensure all initialization is finished.
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::BindOnce(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  base::RunLoop().RunUntilIdle();
+
+  bool invoked = false;
+
+  // Set priority notification, and then register handler. IPH will show up
+  // immediately after registration.
+  tracker_->SetPriorityNotification(kTrackerTestFeatureFoo);
+  tracker_->RegisterPriorityNotificationHandler(
+      kTrackerTestFeatureFoo,
+      base::BindLambdaForTesting([&]() { invoked = true; }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(invoked);
+
+  // Try registering handler once again. The IPH won't show up again since the
+  // notification has been consumed.
+  invoked = false;
+  tracker_->RegisterPriorityNotificationHandler(
+      kTrackerTestFeatureFoo,
+      base::BindLambdaForTesting([&]() { invoked = true; }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(invoked);
+
+  // Set priority notification one more time. Now the IPH will show up.
+  tracker_->SetPriorityNotification(kTrackerTestFeatureFoo);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(invoked);
+}
+
+TEST_F(TrackerImplTest, TestSetPriorityNotificationAfterRegistration) {
+  // Ensure all initialization is finished.
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::BindOnce(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  base::RunLoop().RunUntilIdle();
+
+  bool invoked = false;
+
+  // Register the handler first, and then set priority notification.
+  tracker_->RegisterPriorityNotificationHandler(
+      kTrackerTestFeatureFoo,
+      base::BindLambdaForTesting([&]() { invoked = true; }));
+  tracker_->SetPriorityNotification(kTrackerTestFeatureFoo);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(invoked);
+
+  // Set priority notification again. The IPH won't show up again, since the
+  // handler is good for only one use.
+  invoked = false;
+  tracker_->SetPriorityNotification(kTrackerTestFeatureFoo);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(invoked);
+}
+
+TEST_F(TrackerImplTest, TestUnregisterPriorityNotification) {
+  // Ensure all initialization is finished.
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::BindOnce(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  base::RunLoop().RunUntilIdle();
+
+  bool invoked = false;
+
+  // Register the handler, and unregister before setting the notification. The
+  // IPH won't show up.
+  invoked = false;
+  tracker_->RegisterPriorityNotificationHandler(
+      kTrackerTestFeatureFoo,
+      base::BindLambdaForTesting([&]() { invoked = true; }));
+  tracker_->UnregisterPriorityNotificationHandler(kTrackerTestFeatureFoo);
+  tracker_->SetPriorityNotification(kTrackerTestFeatureFoo);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(invoked);
+}
+
 TEST_F(TrackerImplTest, TestTriggering) {
   // Ensure all initialization is finished.
   StoringInitializedCallback callback;
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java
index 55a1e51..781c303 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java
@@ -116,6 +116,22 @@
         return () -> {};
     }
 
+    @Override
+    public void setPriorityNotification(String feature) {}
+
+    @Override
+    @Nullable
+    public String getPendingPriorityNotification() {
+        return null;
+    }
+
+    @Override
+    public void registerPriorityNotificationHandler(
+            String feature, Runnable priorityNotificationHandler) {}
+
+    @Override
+    public void unregisterPriorityNotificationHandler(String feature) {}
+
     @CalledByNative
     @Override
     public boolean isInitialized() {
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/Tracker.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/Tracker.java
index c54f368..868eeea 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/Tracker.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/Tracker.java
@@ -128,6 +128,31 @@
     DisplayLockHandle acquireDisplayLock();
 
     /**
+     * Called by the client to notify the tracker that a priority notification should be shown. If a
+     * handler has already been registered, the IPH will be shown right away. Otherwise, the tracker
+     * will cache the priority feature and will show the IPH whenever a handler is registered in
+     * future. All other IPHs will be blocked until then.
+     */
+    void setPriorityNotification(String feature);
+
+    /**
+     * Called to check if there is a priority notification scheduled to be shown next. Returns null
+     * if there is none scheduled to be shown or the notification has already been shown.
+     */
+    @Nullable
+    String getPendingPriorityNotification();
+
+    /**
+     * Called by the client to register a handler for priority notifications. This
+     * will essentially contain the code to spin up an IPH. The handler runs only once and
+     * unregisters itself.
+     */
+    void registerPriorityNotificationHandler(String feature, Runnable priorityNotificationHandler);
+
+    /** Unregister the handler. Must be called during client destruction. */
+    void unregisterPriorityNotificationHandler(String feature);
+
+    /**
      * Returns whether the tracker has been successfully initialized. During startup, this will be
      * false until the internal models have been loaded at which point it is set to true if the
      * initialization was successful. The state will never change from initialized to uninitialized.
diff --git a/components/feature_engagement/public/android/wrapping_test_tracker.cc b/components/feature_engagement/public/android/wrapping_test_tracker.cc
index 951bbb75..71c1dd1f 100644
--- a/components/feature_engagement/public/android/wrapping_test_tracker.cc
+++ b/components/feature_engagement/public/android/wrapping_test_tracker.cc
@@ -10,6 +10,7 @@
 #include "base/android/jni_string.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/feature_engagement/public/jni_headers/CppWrappedTestTracker_jni.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 
@@ -91,6 +92,21 @@
   return nullptr;
 }
 
+void WrappingTestTracker::SetPriorityNotification(
+    const base::Feature& feature) {}
+
+absl::optional<std::string>
+WrappingTestTracker::GetPendingPriorityNotification() {
+  return absl::nullopt;
+}
+
+void WrappingTestTracker::RegisterPriorityNotificationHandler(
+    const base::Feature& feature,
+    base::OnceClosure callback) {}
+
+void WrappingTestTracker::UnregisterPriorityNotificationHandler(
+    const base::Feature& feature) {}
+
 bool WrappingTestTracker::IsInitialized() const {
   return Java_CppWrappedTestTracker_isInitialized(
       base::android::AttachCurrentThread(), java_tracker_);
diff --git a/components/feature_engagement/public/android/wrapping_test_tracker.h b/components/feature_engagement/public/android/wrapping_test_tracker.h
index 5fb0f68..ee39acd 100644
--- a/components/feature_engagement/public/android/wrapping_test_tracker.h
+++ b/components/feature_engagement/public/android/wrapping_test_tracker.h
@@ -11,6 +11,7 @@
 #include "base/android/jni_android.h"
 #include "base/callback.h"
 #include "components/feature_engagement/public/tracker.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 // This class wraps a Tracker from Java and forwards to it all calls received.
@@ -37,6 +38,12 @@
   void DismissedWithSnooze(const base::Feature& feature,
                            absl::optional<SnoozeAction> snooze_action) override;
   std::unique_ptr<DisplayLockHandle> AcquireDisplayLock() override;
+  absl::optional<std::string> GetPendingPriorityNotification() override;
+  void SetPriorityNotification(const base::Feature& feature) override;
+  void RegisterPriorityNotificationHandler(const base::Feature& feature,
+                                           base::OnceClosure callback) override;
+  void UnregisterPriorityNotificationHandler(
+      const base::Feature& feature) override;
   bool IsInitialized() const override;
   void AddOnInitializedCallback(OnInitializedCallback callback) override;
 
diff --git a/components/feature_engagement/public/tracker.h b/components/feature_engagement/public/tracker.h
index 17e6ed9b..899eef8 100644
--- a/components/feature_engagement/public/tracker.h
+++ b/components/feature_engagement/public/tracker.h
@@ -16,6 +16,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "build/build_config.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_android.h"
@@ -191,6 +192,27 @@
   // This method returns nullptr if no handle could be retrieved.
   virtual std::unique_ptr<DisplayLockHandle> AcquireDisplayLock() = 0;
 
+  // Called by the client to notify the tracker that a priority notification
+  // should be shown. If a handler has already been registered, the IPH will be
+  // shown right away. Otherwise, the tracker will cache the priority feature
+  // and will show the IPH whenever a handler is registered in future. All other
+  // IPHs will be blocked until then. It isn't allowed to invoke this method
+  // again with another notification before the existing one is processed.
+  virtual void SetPriorityNotification(const base::Feature& feature) = 0;
+
+  // Called to get if there is a pending priority notification to be shown next.
+  virtual absl::optional<std::string> GetPendingPriorityNotification() = 0;
+
+  // Called by the client to register a handler for priority notifications. This
+  // will essentially contain the code to spin up an IPH.
+  virtual void RegisterPriorityNotificationHandler(
+      const base::Feature& feature,
+      base::OnceClosure callback) = 0;
+
+  // Unregister the handler. Must be called during client destruction.
+  virtual void UnregisterPriorityNotificationHandler(
+      const base::Feature& feature) = 0;
+
   // Returns whether the tracker has been successfully initialized. During
   // startup, this will be false until the internal models have been loaded at
   // which point it is set to true if the initialization was successful. The
diff --git a/components/feature_engagement/test/mock_tracker.h b/components/feature_engagement/test/mock_tracker.h
index a4ba043..25aefb2 100644
--- a/components/feature_engagement/test/mock_tracker.h
+++ b/components/feature_engagement/test/mock_tracker.h
@@ -10,6 +10,7 @@
 
 #include "components/feature_engagement/public/tracker.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace feature_engagement {
 namespace test {
@@ -39,6 +40,12 @@
                void(const base::Feature& feature,
                     absl::optional<SnoozeAction> snooze_action));
   MOCK_METHOD0(AcquireDisplayLock, std::unique_ptr<DisplayLockHandle>());
+  MOCK_METHOD1(SetPriorityNotification, void(const base::Feature&));
+  MOCK_METHOD0(GetPendingPriorityNotification, absl::optional<std::string>());
+  MOCK_METHOD2(RegisterPriorityNotificationHandler,
+               void(const base::Feature&, base::OnceClosure));
+  MOCK_METHOD1(UnregisterPriorityNotificationHandler,
+               void(const base::Feature&));
   MOCK_METHOD1(AddOnInitializedCallback, void(OnInitializedCallback callback));
 };
 
diff --git a/components/invalidation/impl/invalidator_registrar_with_memory.cc b/components/invalidation/impl/invalidator_registrar_with_memory.cc
index bead8450..1f35ce4 100644
--- a/components/invalidation/impl/invalidator_registrar_with_memory.cc
+++ b/components/invalidation/impl/invalidator_registrar_with_memory.cc
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/stl_util.h"
 #include "base/values.h"
@@ -55,6 +56,9 @@
 
 }  // namespace
 
+const base::Feature kRestoreInterestingTopicsFeature{
+    "InvalidatorRestoreInterestingTopics", base::FEATURE_ENABLED_BY_DEFAULT};
+
 // static
 void InvalidatorRegistrarWithMemory::RegisterProfilePrefs(
     PrefRegistrySimple* registry) {
@@ -87,16 +91,18 @@
     return;
   }
   // Restore |handler_name_to_subscribed_topics_map_| from prefs.
+  if (!base::FeatureList::IsEnabled(kRestoreInterestingTopicsFeature))
+    return;
   for (auto it : pref_data->DictItems()) {
     const std::string& topic_name = it.first;
     if (it.second.is_dict()) {
-      const base::Value* handler = it.second.FindDictKey(kHandler);
-      const base::Value* is_public = it.second.FindDictKey(kIsPublic);
+      const std::string* handler = it.second.FindStringKey(kHandler);
+      const absl::optional<bool> is_public = it.second.FindBoolKey(kIsPublic);
       if (!handler || !is_public) {
         continue;
       }
-      handler_name_to_subscribed_topics_map_[handler->GetString()].insert(
-          TopicData(topic_name, is_public->GetBool()));
+      handler_name_to_subscribed_topics_map_[*handler].insert(
+          TopicData(topic_name, *is_public));
     } else if (it.second.is_string()) {
       handler_name_to_subscribed_topics_map_[it.second.GetString()].insert(
           TopicData(topic_name, false));
diff --git a/components/invalidation/impl/invalidator_registrar_with_memory_unittest.cc b/components/invalidation/impl/invalidator_registrar_with_memory_unittest.cc
index c92918a7..68352df 100644
--- a/components/invalidation/impl/invalidator_registrar_with_memory_unittest.cc
+++ b/components/invalidation/impl/invalidator_registrar_with_memory_unittest.cc
@@ -6,6 +6,9 @@
 
 #include <memory>
 
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/invalidation/impl/fake_invalidation_handler.h"
 #include "components/invalidation/public/invalidation.h"
 #include "components/invalidation/public/invalidation_util.h"
@@ -243,6 +246,48 @@
   invalidator->UnregisterHandler(&handler1);
 }
 
+TEST(InvalidatorRegistrarWithMemoryTest, RestoresInterestingTopics) {
+  constexpr char kTopicsToHandler[] =
+      "invalidation.per_sender_topics_to_handler";
+
+  const base::Feature restore_interesting_topics_feature{
+      "InvalidatorRestoreInterestingTopics", base::FEATURE_ENABLED_BY_DEFAULT};
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(restore_interesting_topics_feature);
+
+  TestingPrefServiceSimple pref_service;
+  InvalidatorRegistrarWithMemory::RegisterProfilePrefs(pref_service.registry());
+
+  // Set up some previously-registered topics in the pref.
+  constexpr char kStoredTopicsJson[] =
+      R"({"sender_id": {
+            "topic_1": {"handler": "handler_1", "is_public": true},
+            "topic_2": {"handler": "handler_2", "is_public": true},
+            "topic_3": "handler_3",
+            "topic_4_1": {"handler": "handler_4", "is_public": false},
+            "topic_4_2": {"handler": "handler_4", "is_public": false},
+            "topic_4_3": {"handler": "handler_4", "is_public": false}
+      }})";
+
+  absl::optional<base::Value> stored_topics =
+      base::JSONReader::Read(kStoredTopicsJson);
+  ASSERT_TRUE(stored_topics);
+  pref_service.Set(kTopicsToHandler, std::move(*stored_topics));
+
+  // Create an invalidator and make sure it correctly restored state from the
+  // pref.
+  auto invalidator = std::make_unique<InvalidatorRegistrarWithMemory>(
+      &pref_service, "sender_id", /*migrate_old_prefs=*/false);
+
+  std::map<std::string, TopicMetadata> expected_subscribed_topics{
+      {"topic_1", TopicMetadata{true}},    {"topic_2", TopicMetadata{true}},
+      {"topic_3", TopicMetadata{false}},   {"topic_4_1", TopicMetadata{false}},
+      {"topic_4_2", TopicMetadata{false}}, {"topic_4_3", TopicMetadata{false}},
+  };
+
+  EXPECT_EQ(expected_subscribed_topics, invalidator->GetAllSubscribedTopics());
+}
+
 }  // namespace
 
 }  // namespace invalidation
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index 48e2b09..d3ba5ba6 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -98,7 +98,8 @@
     const base::FilePath& database_dir,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
     : last_annotated_history_visits_(
-          features::MaxContentAnnotationRequestsCached()) {
+          features::MaxContentAnnotationRequestsCached()),
+      annotated_text_cache_(features::MaxVisitAnnotationCacheSize()) {
   DCHECK(optimization_guide_model_provider);
   DCHECK(history_service);
   history_service_ = history_service;
@@ -143,7 +144,25 @@
   // Used for testing.
   LOCAL_HISTOGRAM_BOOLEAN(
       "PageContentAnnotations.AnnotateVisit.AnnotationRequested", true);
+
+  if (annotated_text_cache_.Peek(*visit.text_to_annotate) !=
+      annotated_text_cache_.end()) {
+    // We have annotations the text for this visit, so return that immediately
+    // rather than re-executing the model.
+    //
+    // TODO(crbug.com/1291275): If the model was updated, the cached value could
+    // be stale so we should invalidate the cache on model updates.
+    OnPageContentAnnotated(
+        visit, annotated_text_cache_.Get(*visit.text_to_annotate)->second);
+    base::UmaHistogramBoolean(
+        "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+        true);
+    return;
+  }
   visits_to_annotate_.emplace_back(visit);
+  base::UmaHistogramBoolean(
+      "OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached",
+      false);
   if (visits_to_annotate_.size() >= features::AnnotateVisitBatchSize()) {
     if (current_visit_annotation_batch_.empty()) {
       // Used for testing.
@@ -260,6 +279,10 @@
   if (!content_annotations)
     return;
 
+  if (annotated_text_cache_.Peek(*visit.text_to_annotate) ==
+      annotated_text_cache_.end()) {
+    annotated_text_cache_.Put(*visit.text_to_annotate, *content_annotations);
+  }
   MaybeRecordVisibilityUKM(visit, content_annotations);
 
   if (!features::ShouldWriteContentAnnotationsToHistoryService())
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h
index 0d490fa..278a005 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -225,6 +225,11 @@
   base::LRUCache<HistoryVisit, bool, HistoryVisit::Comp>
       last_annotated_history_visits_;
 
+  // A LRU cache of the annotation results for visits. If the text of the visit
+  // is in the cache, the cached model annotations will be used.
+  base::HashingLRUCache<std::string, history::VisitContentModelAnnotations>
+      annotated_text_cache_;
+
   // The set of visits to be annotated, this is added to by Annotate requests
   // from the web content observer. These will be annotated when the set is full
   // and annotations can be scheduled with minimal impact to browsing.
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 13b1cb6b..1abf629 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -550,5 +550,11 @@
   return std::max(1, batch_size);
 }
 
+size_t MaxVisitAnnotationCacheSize() {
+  int batch_size = GetFieldTrialParamByFeatureAsInt(
+      kPageContentAnnotations, "max_visit_annotation_cache_size", 50);
+  return std::max(1, batch_size);
+}
+
 }  // namespace features
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index d12ff9a..8eb0a55 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -269,6 +269,9 @@
 // The size of batches to run for validation.
 size_t BatchAnnotationsValidationBatchSize();
 
+// The maximum size of the visit annotation cache.
+size_t MaxVisitAnnotationCacheSize();
+
 }  // namespace features
 }  // namespace optimization_guide
 
diff --git a/components/reporting/storage/BUILD.gn b/components/reporting/storage/BUILD.gn
index 0f201eda..bf3ea44 100644
--- a/components/reporting/storage/BUILD.gn
+++ b/components/reporting/storage/BUILD.gn
@@ -44,6 +44,7 @@
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
     "//components/reporting/resources:resource_interface",
+    "//components/reporting/util:file",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//components/reporting/util:task_runner_context",
@@ -71,6 +72,7 @@
     "//components/reporting/encryption:verification",
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
+    "//components/reporting/util:file",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//components/reporting/util:task_runner_context",
diff --git a/components/reporting/storage/storage.cc b/components/reporting/storage/storage.cc
index ee92940..40257243 100644
--- a/components/reporting/storage/storage.cc
+++ b/components/reporting/storage/storage.cc
@@ -33,6 +33,7 @@
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_queue.h"
 #include "components/reporting/storage/storage_uploader_interface.h"
+#include "components/reporting/util/file.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
@@ -345,7 +346,7 @@
       if (full_name == signed_encryption_key_result.value().first) {
         continue;  // This file is used.
       }
-      base::DeleteFile(full_name);  // Ignore errors, if any.
+      DeleteFileWarnIfFailed(full_name);  // Ignore errors, if any.
     }
 
     // Return the key.
@@ -445,7 +446,7 @@
     }
     // Delete all files assigned for deletion.
     for (const auto& file_to_remove : key_files_to_remove) {
-      base::DeleteFile(file_to_remove);  // Ignore errors, if any.
+      DeleteFileWarnIfFailed(file_to_remove);  // Ignore errors, if any.
     }
   }
 
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index b48f061aa..f786258f 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -42,6 +42,7 @@
 #include "components/reporting/resources/resource_interface.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_uploader_interface.h"
+#include "components/reporting/util/file.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
@@ -758,8 +759,8 @@
     }
   }
   for (const auto& file_to_delete : files_to_delete) {
-    // Ignore result. If it fails, the file will be naturally handled next time.
-    base::DeleteFile(file_to_delete);
+    // If it fails, the file will be naturally handled next time.
+    DeleteFileWarnIfFailed(file_to_delete);
   }
 }
 
@@ -790,8 +791,8 @@
   for (const auto& file_to_delete : files_to_delete) {
     // Delete file on disk. Note: disk space has already been released when the
     // metafile was destructed, and so we don't need to do that here.
-    // Ignore result. If it fails, the file will be naturally handled next time.
-    base::DeleteFile(file_to_delete);
+    // If it fails, the file will be naturally handled next time.
+    DeleteFileWarnIfFailed(file_to_delete);
   }
 }
 
@@ -1635,7 +1636,7 @@
     // Current file holds only ids <= sequencing_id.
     // Delete it.
     files_.begin()->second->Close();
-    files_.begin()->second->Delete();  // ignore results
+    files_.begin()->second->DeleteWarnIfFailed();
     files_.erase(files_.begin());
   }
   // Even if there were errors, ignore them.
@@ -1747,15 +1748,11 @@
   }
 }
 
-Status StorageQueue::SingleFile::Delete() {
+void StorageQueue::SingleFile::DeleteWarnIfFailed() {
   DCHECK(!handle_);
   GetDiskResource()->Discard(size_);
   size_ = 0;
-  if (!base::DeleteFile(filename_)) {
-    return Status(error::DATA_LOSS,
-                  base::StrCat({"Cannot delete file=", name()}));
-  }
-  return Status::StatusOK();
+  DeleteFileWarnIfFailed(filename_);
 }
 
 StatusOr<base::StringPiece> StorageQueue::SingleFile::Read(
diff --git a/components/reporting/storage/storage_queue.h b/components/reporting/storage/storage_queue.h
index 4f367ed9..88619b3 100644
--- a/components/reporting/storage/storage_queue.h
+++ b/components/reporting/storage/storage_queue.h
@@ -151,7 +151,7 @@
     Status Open(bool read_only);  // No-op if already opened.
     void Close();                 // No-op if not opened.
 
-    Status Delete();
+    void DeleteWarnIfFailed();
 
     // Attempts to read |size| bytes from position |pos| and returns
     // reference to the data that were actually read (no more than |size|).
diff --git a/components/reporting/util/BUILD.gn b/components/reporting/util/BUILD.gn
index a5cacd7..5b45f1d 100644
--- a/components/reporting/util/BUILD.gn
+++ b/components/reporting/util/BUILD.gn
@@ -15,6 +15,14 @@
   deps = [ "//net" ]
 }
 
+static_library("file") {
+  sources = [
+    "file.cc",
+    "file.h",
+  ]
+  deps = [ "//base" ]
+}
+
 source_set("shared_vector") {
   sources = [ "shared_vector.h" ]
   deps = [
@@ -73,6 +81,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "file_unittest.cc",
     "shared_queue_unittest.cc",
     "shared_vector_unittest.cc",
     "status_macros_unittest.cc",
@@ -80,6 +89,7 @@
     "statusor_unittest.cc",
   ]
   deps = [
+    ":file",
     ":shared_queue",
     ":shared_vector",
     ":status",
diff --git a/components/reporting/util/file.cc b/components/reporting/util/file.cc
new file mode 100644
index 0000000..02411606
--- /dev/null
+++ b/components/reporting/util/file.cc
@@ -0,0 +1,20 @@
+// Copyright 2022 The Chromium 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/reporting/util/file.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+
+namespace reporting {
+
+bool DeleteFileWarnIfFailed(const base::FilePath& path) {
+  const auto delete_result = base::DeleteFile(path);
+  if (!delete_result) {
+    LOG(WARNING) << "Failed to delete " << path.MaybeAsASCII();
+  }
+  return delete_result;
+}
+
+}  // namespace reporting
diff --git a/components/reporting/util/file.h b/components/reporting/util/file.h
new file mode 100644
index 0000000..9825441f
--- /dev/null
+++ b/components/reporting/util/file.h
@@ -0,0 +1,22 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Utilities for file operations.
+
+#ifndef COMPONENTS_REPORTING_UTIL_FILE_H_
+#define COMPONENTS_REPORTING_UTIL_FILE_H_
+
+#include "base/files/file_util.h"
+
+namespace reporting {
+
+// Deletes the given path, whether it's a file or a directory.
+// This function is identical to base::DeleteFile() except that it issues a
+// warning if the deletion fails. Useful when we do not care about whether the
+// deletion succeeds or not.
+bool DeleteFileWarnIfFailed(const base::FilePath& path);
+
+}  // namespace reporting
+
+#endif  // COMPONENTS_REPORTING_UTIL_FILE_H_
diff --git a/components/reporting/util/file_unittest.cc b/components/reporting/util/file_unittest.cc
new file mode 100644
index 0000000..2f47eb4
--- /dev/null
+++ b/components/reporting/util/file_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium 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/reporting/util/file.h"
+
+#include "base/files/file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/test_file_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace reporting {
+namespace {
+
+TEST(FileTest, DeleteFileWarnIfFailed) {
+  // This test briefly tests DeleteFileWarnIfFailed, as it mostly calls
+  // DeleteFile(), which should be more extensively tested in base.
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  const auto dir_path = temp_dir.GetPath();
+  ASSERT_TRUE(base::DirectoryExists(dir_path));
+
+  base::FilePath file_path;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(dir_path, &file_path));
+
+  // Delete an existing file with no permission.
+  // Don't test on Fuchsia: No file permission support. See
+  // base/files/file_util_unittest.cc for some similar tests being skipped.
+#if !BUILDFLAG(IS_FUCHSIA)
+  {
+    // On Windows, we open the file to prevent it from being deleted. Otherwise,
+    // we modify the directory permission to prevent it from being deleted.
+#if BUILDFLAG(IS_WIN)
+    base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+    ASSERT_TRUE(file.IsValid());
+#else   // BUILDFLAG(IS_WIN)
+    base::FilePermissionRestorer restore_permissions_for(dir_path);
+    // Get rid of the write permission from temp_dir
+    ASSERT_TRUE(base::MakeFileUnwritable(dir_path));
+    // Ensure no deletion permission
+    ASSERT_FALSE(base::PathIsWritable(dir_path));
+#endif  // BUILDFLAG(IS_WIN)
+    ASSERT_TRUE(base::PathExists(file_path));
+    ASSERT_FALSE(DeleteFileWarnIfFailed(file_path))
+        << "Deletion of an existing file without permission should fail";
+  }
+#endif  // !BUILDFLAG(IS_FUCHSIA)
+
+  {
+    // Delete with permission
+    ASSERT_TRUE(base::PathIsWritable(dir_path));  // Ensure deletion permission
+    ASSERT_TRUE(base::PathExists(file_path));
+    ASSERT_TRUE(DeleteFileWarnIfFailed(file_path))
+        << "Deletion of an existing file should succeed";
+    ASSERT_FALSE(base::PathExists(file_path)) << "File failed to be deleted";
+  }
+
+  // Delete a non-existing file
+  {
+    ASSERT_FALSE(base::PathExists(file_path));
+    ASSERT_TRUE(DeleteFileWarnIfFailed(file_path))
+        << "Deletion of a nonexisting file should succeed";
+  }
+}
+
+}  // namespace
+}  // namespace reporting
diff --git a/components/url_pattern_index/url_pattern_index.cc b/components/url_pattern_index/url_pattern_index.cc
index 387c93ca..e6a6f68 100644
--- a/components/url_pattern_index/url_pattern_index.cc
+++ b/components/url_pattern_index/url_pattern_index.cc
@@ -792,6 +792,11 @@
     const flat::UrlPatternIndex* flat_index)
     : flat_index_(flat_index) {
   DCHECK(!flat_index || flat_index->n() == kNGramSize);
+  // Speculative investigation for crash (see crbug.com/1286207): check that we
+  // can access the ngram_index on each UrlPatternIndexMatcher without failure.
+  if (flat_index) {
+    CHECK_GT(flat_index->ngram_index()->size(), 0u);
+  }
 }
 
 UrlPatternIndexMatcher::~UrlPatternIndexMatcher() = default;
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 176f1bde..39e9b18c 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -123,9 +123,10 @@
 const int kCompatibleVersionNumber = 15;
 
 // Latest version of the database that cannot be upgraded to
-// |kCurrentVersionNumber| without razing the database. No versions are
-// currently deprecated.
-const int kDeprecatedVersionNumber = 0;
+// |kCurrentVersionNumber| without razing the database.
+//
+// Versions 1-14 were deprecated by https://crrev.com/c/3421175.
+const int kDeprecatedVersionNumber = 14;
 
 void RecordInitializationStatus(
     const AttributionStorageSql::InitStatus status) {
@@ -1475,8 +1476,7 @@
     return CreateSchema();
   }
 
-  return UpgradeAttributionStorageSqlSchema(db_.get(), &meta_table_,
-                                            delegate_.get());
+  return UpgradeAttributionStorageSqlSchema(db_.get(), &meta_table_);
 }
 
 bool AttributionStorageSql::CreateSchema() {
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_migrations.cc b/content/browser/attribution_reporting/attribution_storage_sql_migrations.cc
index 9bb8f608..996713f 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_migrations.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_migrations.cc
@@ -4,1292 +4,19 @@
 
 #include "content/browser/attribution_reporting/attribution_storage_sql_migrations.h"
 
-#include <vector>
-
-#include "base/guid.h"
+#include "base/check.h"
 #include "base/metrics/histogram_functions.h"
-#include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/common_source_info.h"
-#include "content/browser/attribution_reporting/sql_utils.h"
-#include "content/browser/attribution_reporting/stored_source.h"
-#include "net/base/schemeful_site.h"
-#include "sql/database.h"
-#include "sql/meta_table.h"
-#include "sql/statement.h"
-#include "sql/transaction.h"
-#include "url/origin.h"
+#include "base/time/time.h"
 
 namespace content {
 
-namespace {
-
-StoredSource::Id NextImpressionId(StoredSource::Id id) {
-  return StoredSource::Id(*id + 1);
-}
-
-AttributionReport::EventLevelData::Id NextConversionId(
-    AttributionReport::EventLevelData::Id id) {
-  return AttributionReport::EventLevelData::Id(*id + 1);
-}
-
-struct ImpressionIdAndConversionOrigin {
-  StoredSource::Id source_id;
-  url::Origin conversion_origin;
-};
-
-std::vector<ImpressionIdAndConversionOrigin>
-GetImpressionIdAndConversionOrigins(sql::Database* db,
-                                    StoredSource::Id start_source_id) {
-  static constexpr char kGetImpressionsSql[] =
-      "SELECT impression_id,conversion_origin "
-      "FROM impressions "
-      "WHERE impression_id >= ? "
-      "ORDER BY impression_id "
-      "LIMIT ?";
-
-  sql::Statement statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql));
-  statement.BindInt64(0, *start_source_id);
-
-  const int kNumImpressions = 100;
-  statement.BindInt(1, kNumImpressions);
-
-  std::vector<ImpressionIdAndConversionOrigin> impressions;
-  while (statement.Step()) {
-    StoredSource::Id source_id(statement.ColumnInt64(0));
-    url::Origin conversion_origin =
-        DeserializeOrigin(statement.ColumnString(1));
-
-    impressions.push_back({source_id, std::move(conversion_origin)});
-  }
-  if (!statement.Succeeded())
-    return {};
-  return impressions;
-}
-
-struct ImpressionIdAndImpressionOrigin {
-  StoredSource::Id source_id;
-  url::Origin impression_origin;
-};
-
-std::vector<ImpressionIdAndImpressionOrigin>
-GetImpressionIdAndImpressionOrigins(sql::Database* db,
-                                    StoredSource::Id start_source_id) {
-  static constexpr char kGetImpressionsSql[] =
-      "SELECT impression_id,impression_origin "
-      "FROM impressions "
-      "WHERE impression_id >= ? "
-      "ORDER BY impression_id "
-      "LIMIT ?";
-
-  sql::Statement statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql));
-  statement.BindInt64(0, *start_source_id);
-
-  const int kNumImpressions = 100;
-  statement.BindInt(1, kNumImpressions);
-
-  std::vector<ImpressionIdAndImpressionOrigin> impressions;
-  while (statement.Step()) {
-    StoredSource::Id source_id(statement.ColumnInt64(0));
-    url::Origin impression_origin =
-        DeserializeOrigin(statement.ColumnString(1));
-
-    impressions.push_back({source_id, std::move(impression_origin)});
-  }
-  if (!statement.Succeeded())
-    return {};
-  return impressions;
-}
-
-std::vector<AttributionReport::EventLevelData::Id> GetConversionIds(
-    sql::Database* db,
-    AttributionReport::EventLevelData::Id start_conversion_id) {
-  static constexpr char kGetConversionsSql[] =
-      "SELECT conversion_id FROM conversions "
-      "WHERE conversion_id >= ? "
-      "ORDER BY conversion_id "
-      "LIMIT ?";
-
-  sql::Statement statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kGetConversionsSql));
-  statement.BindInt64(0, *start_conversion_id);
-
-  const int kNumConversions = 100;
-  statement.BindInt(1, kNumConversions);
-
-  std::vector<AttributionReport::EventLevelData::Id> conversion_ids;
-  while (statement.Step()) {
-    conversion_ids.emplace_back(statement.ColumnInt64(0));
-  }
-  if (!statement.Succeeded())
-    return {};
-  return conversion_ids;
-}
-
-bool MigrateToVersion2(sql::Database* db, sql::MetaTable* meta_table) {
-  // Wrap each migration in its own transaction. This results in smaller
-  // transactions, so it's less likely that a transaction's buffer will need to
-  // spill to disk. Also, if the database grows a lot and Chrome stops (user
-  // quit, process kill, etc.) during the migration process, per-migration
-  // transactions make it more likely that we'll make forward progress each time
-  // Chrome stops.
-  sql::Transaction transaction(db);
-  if (!transaction.Begin())
-    return false;
-
-  // Add a new conversion_destination column to the impressions table. This
-  // follows the steps documented at
-  // https://sqlite.org/lang_altertable.html#otheralter. Other approaches, like
-  // using "ALTER ... ADD COLUMN" require setting a DEFAULT value for the column
-  // which is undesirable.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_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)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting a placeholder for
-  // the conversion_destination column.
-  static constexpr char kPopulateNewImpressionTableSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,impression_data,impression_origin,"
-      "conversion_origin,reporting_origin,impression_time,"
-      "expiry_time,num_conversions,active,'' "
-      "FROM impressions";
-  if (!db->Execute(kPopulateNewImpressionTableSql))
-    return false;
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Update each of the impression rows to have the correct associated
-  // conversion_destination. This is only relevant for active impressions, as
-  // the column is only used for matching impressions to conversions, but we
-  // update all impressions regardless.
-  //
-  // We update a subset of rows at a time to avoid pulling the entire
-  // impressions table into memory.
-  std::vector<ImpressionIdAndConversionOrigin> impressions =
-      GetImpressionIdAndConversionOrigins(db, StoredSource::Id(0));
-
-  static constexpr char kUpdateDestinationSql[] =
-      "UPDATE impressions SET conversion_destination = ? WHERE impression_id = "
-      "?";
-  sql::Statement update_destination_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kUpdateDestinationSql));
-
-  while (!impressions.empty()) {
-    // Perform the column updates for each row we pulled into memory.
-    for (const auto& impression : impressions) {
-      update_destination_statement.Reset(/*clear_bound_vars=*/true);
-
-      // The conversion destination is derived from the conversion origin
-      // dynamically.
-      update_destination_statement.BindString(
-          0, net::SchemefulSite(impression.conversion_origin).Serialize());
-      update_destination_statement.BindInt64(1, *impression.source_id);
-      update_destination_statement.Run();
-    }
-
-    // Fetch the next batch of rows from the database.
-    impressions = GetImpressionIdAndConversionOrigins(
-        db, NextImpressionId(impressions.back().source_id));
-  }
-
-  // Create the pre-existing impression table indices on the new table.
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  // Replace the pre-existing conversion_origin_idx with an index that uses the
-  // conversion destination, as attribution logic now depends on the
-  // conversion_destination.
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(2);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion3(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;
-
-  // Add new source_type and attributed_truthfully columns to the impressions
-  // table. This follows the steps documented at
-  // https://sqlite.org/lang_altertable.html#otheralter. Other approaches, like
-  // using "ALTER ... ADD COLUMN" require setting a DEFAULT value for the column
-  // which is undesirable.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_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)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting default values for
-  // the source_type and attributed_truthfully columns.
-  static constexpr char kPopulateNewImpressionTableSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,impression_data,impression_origin,"
-      "conversion_origin,reporting_origin,impression_time,"
-      "expiry_time,num_conversions,active,conversion_destination,?,? "
-      "FROM impressions";
-  sql::Statement populate_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionTableSql));
-  // Only navigation type was supported prior to this column being added.
-  populate_statement.BindInt(
-      0, static_cast<int>(CommonSourceInfo::SourceType::kNavigation));
-  populate_statement.BindBool(1, true);
-  if (!populate_statement.Run())
-    return false;
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Create the pre-existing impression table indices on the new table.
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(3);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion4(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;
-
-  static constexpr char kRateLimitTableSql[] =
-      "CREATE TABLE IF NOT EXISTS 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)";
-  if (!db->Execute(kRateLimitTableSql))
-    return false;
-
-  static constexpr char kRateLimitImpressionSiteTypeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS rate_limit_impression_site_type_idx "
-      "ON rate_limits(attribution_type,conversion_destination,"
-      "impression_site,conversion_time)";
-  if (!db->Execute(kRateLimitImpressionSiteTypeIndexSql))
-    return false;
-
-  static constexpr char kRateLimitConversionTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS rate_limit_conversion_time_idx "
-      "ON rate_limits(conversion_time)";
-  if (!db->Execute(kRateLimitConversionTimeIndexSql))
-    return false;
-
-  static constexpr char kRateLimitImpressionIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS rate_limit_impression_id_idx "
-      "ON rate_limits(impression_id)";
-  if (!db->Execute(kRateLimitImpressionIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(4);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion5(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.
-  static constexpr char kDropZeroCreditConversionsSql[] =
-      "DELETE FROM conversions WHERE attribution_credit = 0";
-  if (!db->Execute(kDropZeroCreditConversionsSql))
-    return false;
-
-  static constexpr char kDropAttributionCreditColumnSql[] =
-      "ALTER TABLE conversions DROP COLUMN attribution_credit";
-  if (!db->Execute(kDropAttributionCreditColumnSql))
-    return false;
-
-  meta_table->SetVersionNumber(5);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion6(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;
-
-  // Add new priority column to the impressions table. This follows the steps
-  // documented at https://sqlite.org/lang_altertable.html#otheralter. Other
-  // approaches, like using "ALTER ... ADD COLUMN" require setting a DEFAULT
-  // value for the column which is undesirable.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_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,"
-      "priority INTEGER NOT NULL)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting default values for
-  // the priority column.
-  static constexpr char kPopulateNewImpressionTableSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,impression_data,impression_origin,"
-      "conversion_origin,reporting_origin,impression_time,"
-      "expiry_time,num_conversions,active,conversion_destination,source_type,"
-      "attributed_truthfully,0 "
-      "FROM impressions";
-  sql::Statement populate_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionTableSql));
-  if (!populate_statement.Run())
-    return false;
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Create the pre-existing impression table indices on the new table.
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(6);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion7(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;
-
-  // Add new impression_site column to the impressions table. This follows the
-  // steps documented at https://sqlite.org/lang_altertable.html#otheralter.
-  // Other approaches, like using "ALTER ... ADD COLUMN" require setting a
-  // DEFAULT value for the column which is undesirable.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_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,"
-      "priority INTEGER NOT NULL,"
-      "impression_site TEXT NOT NULL)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting placeholder values
-  // for the impression_site column.
-  static constexpr char kPopulateNewImpressionTableSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,impression_data,impression_origin,"
-      "conversion_origin,reporting_origin,impression_time,"
-      "expiry_time,num_conversions,active,conversion_destination,source_type,"
-      "attributed_truthfully,priority,'' "
-      "FROM impressions";
-  sql::Statement populate_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionTableSql));
-  if (!populate_statement.Run())
-    return false;
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Update each of the impression rows to have the correct associated
-  // impression_site.
-  //
-  // We update a subset of rows at a time to avoid pulling the entire
-  // impressions table into memory.
-  std::vector<ImpressionIdAndImpressionOrigin> impressions =
-      GetImpressionIdAndImpressionOrigins(db, StoredSource::Id(0));
-
-  static constexpr char kUpdateImpressionSiteSql[] =
-      "UPDATE impressions SET impression_site = ? WHERE impression_id = ?";
-  sql::Statement update_impression_site_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kUpdateImpressionSiteSql));
-
-  while (!impressions.empty()) {
-    // Perform the column updates for each row we pulled into memory.
-    for (const auto& impression : impressions) {
-      update_impression_site_statement.Reset(/*clear_bound_vars=*/true);
-
-      // The impression site is derived from the impression origin dynamically.
-      update_impression_site_statement.BindString(
-          0, net::SchemefulSite(impression.impression_origin).Serialize());
-      update_impression_site_statement.BindInt64(1, *impression.source_id);
-      if (!update_impression_site_statement.Run())
-        return false;
-    }
-
-    // Fetch the next batch of rows from the database.
-    impressions = GetImpressionIdAndImpressionOrigins(
-        db, NextImpressionId(impressions.back().source_id));
-  }
-
-  // Create the pre-existing impression table indices on the new table.
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  // Create the new impression table index.
-  static constexpr char kImpressionSiteIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_site_idx "
-      "ON impressions(active,impression_site,source_type)";
-  if (!db->Execute(kImpressionSiteIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(7);
-  return transaction.Commit();
-}
-
-struct ImpressionIdAndImpressionData {
-  StoredSource::Id source_id;
-  std::string impression_data;
-};
-
-std::vector<ImpressionIdAndImpressionData> GetImpressionIdAndImpressionData(
-    sql::Database* db,
-    StoredSource::Id start_source_id) {
-  static constexpr char kGetImpressionsSql[] =
-      "SELECT impression_id,impression_data "
-      "FROM impressions "
-      "WHERE impression_id >= ? "
-      "ORDER BY impression_id "
-      "LIMIT ?";
-
-  sql::Statement statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql));
-  statement.BindInt64(0, *start_source_id);
-
-  const int kNumImpressions = 100;
-  statement.BindInt(1, kNumImpressions);
-
-  std::vector<ImpressionIdAndImpressionData> impressions;
-  while (statement.Step()) {
-    StoredSource::Id source_id(statement.ColumnInt64(0));
-    std::string impression_data = statement.ColumnString(1);
-
-    impressions.push_back({source_id, std::move(impression_data)});
-  }
-  if (!statement.Succeeded())
-    return {};
-  return impressions;
-}
-
-bool MigrateToVersion8(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;
-
-  // Change the impression_data column from TEXT to INTEGER. This follows the
-  // steps documented at https://sqlite.org/lang_altertable.html#otheralter.
-  // Other approaches, like using "ALTER ... ADD COLUMN" require setting a
-  // DEFAULT value for the column which is undesirable.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_impressions"
-      "(impression_id INTEGER PRIMARY KEY,"
-      "impression_data INTEGER 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,"
-      "priority INTEGER NOT NULL,"
-      "impression_site TEXT NOT NULL)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing impressions rows to the new table with a placeholder
-  // for the impression_data column.
-  static constexpr char kPopulateNewImpressionsSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,0,impression_origin,conversion_origin,reporting_origin,"
-      "impression_time,expiry_time,num_conversions,active,"
-      "conversion_destination,source_type,attributed_truthfully,priority,"
-      "impression_site FROM impressions";
-  sql::Statement populate_new_impressions_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionsSql));
-  if (!populate_new_impressions_statement.Run())
-    return false;
-
-  // Update each of the impression rows to have the correct associated
-  // impression_data. We can't use the CAST SQL function here because it
-  // doesn't support the full range of `uint64_t`.
-  //
-  // We update a subset of rows at a time to avoid pulling the entire
-  // impressions table into memory.
-  std::vector<ImpressionIdAndImpressionData> impressions =
-      GetImpressionIdAndImpressionData(db, StoredSource::Id(0));
-
-  static constexpr char kUpdateImpressionDataSql[] =
-      "UPDATE new_impressions SET impression_data = ? WHERE impression_id = ?";
-  sql::Statement update_impression_data_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kUpdateImpressionDataSql));
-
-  while (!impressions.empty()) {
-    // Perform the column updates for each row we pulled into memory.
-    for (const auto& impression : impressions) {
-      // If we can't parse the data, skip the update to leave the column as 0.
-      uint64_t impression_data = 0u;
-      if (!base::StringToUint64(impression.impression_data, &impression_data))
-        continue;
-
-      update_impression_data_statement.Reset(/*clear_bound_vars=*/true);
-      update_impression_data_statement.BindInt64(
-          0, SerializeUint64(impression_data));
-      update_impression_data_statement.BindInt64(1, *impression.source_id);
-      update_impression_data_statement.Run();
-    }
-
-    // Fetch the next batch of rows from the database.
-    impressions = GetImpressionIdAndImpressionData(
-        db, NextImpressionId(impressions.back().source_id));
-  }
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Create the pre-existing impression table indices on the new table.
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  static constexpr char kImpressionSiteIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_site_idx "
-      "ON impressions(active,impression_site,source_type)";
-  if (!db->Execute(kImpressionSiteIndexSql))
-    return false;
-
-  // Change the conversion_data column from TEXT to INTEGER and make
-  // impression_id NOT NULL. This follows the steps documented at
-  // https://sqlite.org/lang_altertable.html#otheralter./ Other approaches, like
-  // using "ALTER ... ADD COLUMN" require setting a DEFAULT value for the column
-  // which is undesirable.
-  static constexpr char kNewConversionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_conversions"
-      "(conversion_id INTEGER PRIMARY KEY,"
-      "impression_id INTEGER NOT NULL,"
-      "conversion_data INTEGER NOT NULL,"
-      "conversion_time INTEGER NOT NULL,"
-      "report_time INTEGER NOT NULL)";
-  if (!db->Execute(kNewConversionTableSql))
-    return false;
-
-  // Transfer the existing conversions rows to the new table. See
-  // https://www.sqlite.org/lang_expr.html#castexpr for details on CAST, which
-  // we can use here because valid conversion_data is in the range [0, 8].
-  // Existing impression_id values should never be NULL, but if they are, we
-  // insert 0 instead of failing.
-  static constexpr char kPopulateNewConversionsSql[] =
-      "INSERT INTO new_conversions SELECT "
-      "conversion_id,IFNULL(impression_id,0),"
-      "CAST(conversion_data AS INTEGER),conversion_time,report_time "
-      "FROM conversions";
-  sql::Statement populate_new_conversions_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewConversionsSql));
-  if (!populate_new_conversions_statement.Run())
-    return false;
-
-  static constexpr char kDropOldConversionTableSql[] = "DROP TABLE conversions";
-  if (!db->Execute(kDropOldConversionTableSql))
-    return false;
-
-  static constexpr char kRenameConversionTableSql[] =
-      "ALTER TABLE new_conversions RENAME TO conversions";
-  if (!db->Execute(kRenameConversionTableSql))
-    return false;
-
-  // Create the pre-existing conversion table indices on the new table.
-  static constexpr char kConversionReportTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_report_idx "
-      "ON conversions(report_time)";
-  if (!db->Execute(kConversionReportTimeIndexSql))
-    return false;
-
-  static constexpr char kConversionClickIdIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx "
-      "ON conversions(impression_id)";
-  if (!db->Execute(kConversionClickIdIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(8);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion9(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;
-
-  // Add new priority column to the conversions table. This follows the
-  // steps documented at https://sqlite.org/lang_altertable.html#otheralter.
-  // Other approaches, like using "ALTER ... ADD COLUMN" require setting a
-  // DEFAULT value for the column which is undesirable.
-  static constexpr char kNewTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_conversions"
-      "(conversion_id INTEGER PRIMARY KEY,"
-      "impression_id INTEGER NOT NULL,"
-      "conversion_data INTEGER NOT NULL,"
-      "conversion_time INTEGER NOT NULL,"
-      "report_time INTEGER NOT NULL,"
-      "priority INTEGER NOT NULL)";
-  if (!db->Execute(kNewTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting 0 for the priority
-  // column.
-  static constexpr char kPopulateSql[] =
-      "INSERT INTO new_conversions SELECT "
-      "conversion_id,impression_id,conversion_data,conversion_time,"
-      "report_time,0 "
-      "FROM conversions";
-  sql::Statement populate_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateSql));
-  if (!populate_statement.Run())
-    return false;
-
-  static constexpr char kDropOldTableSql[] = "DROP TABLE conversions";
-  if (!db->Execute(kDropOldTableSql))
-    return false;
-
-  static constexpr char kRenameTableSql[] =
-      "ALTER TABLE new_conversions RENAME TO conversions";
-  if (!db->Execute(kRenameTableSql))
-    return false;
-
-  // Create the pre-existing conversion table indices on the new table.
-  static constexpr char kConversionReportTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_report_idx "
-      "ON conversions(report_time)";
-  if (!db->Execute(kConversionReportTimeIndexSql))
-    return false;
-
-  static constexpr char kConversionClickIdIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx "
-      "ON conversions(impression_id)";
-  if (!db->Execute(kConversionClickIdIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(9);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion10(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;
-
-  static constexpr char kDedupKeyTableSql[] =
-      "CREATE TABLE IF NOT EXISTS dedup_keys"
-      "(impression_id INTEGER NOT NULL,"
-      "dedup_key INTEGER NOT NULL,"
-      "PRIMARY KEY(impression_id,dedup_key))WITHOUT ROWID";
-  if (!db->Execute(kDedupKeyTableSql))
-    return false;
-
-  meta_table->SetVersionNumber(10);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion11(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;
-
-  static constexpr char kDropOldImpressionSiteIdxSql[] =
-      "DROP INDEX impression_site_idx";
-  if (!db->Execute(kDropOldImpressionSiteIdxSql))
-    return false;
-
-  static constexpr char kEventSourceImpressionSiteIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS event_source_impression_site_idx "
-      "ON impressions(impression_site)"
-      "WHERE active = 1 AND num_conversions = 0 AND source_type = 1";
-  if (!db->Execute(kEventSourceImpressionSiteIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(11);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion12(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;
-
-  static constexpr char kNewRateLimitTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_rate_limits"
-      "(rate_limit_id INTEGER PRIMARY KEY NOT NULL,"
-      "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,"
-      "bucket TEXT NOT NULL,"
-      "value INTEGER NOT NULL)";
-  if (!db->Execute(kNewRateLimitTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table, inserting 0 for `bucket` and
-  // 1 for `value`, since all existing rows are non-aggregate.
-  static constexpr char kPopulateNewRateLimitTableSql[] =
-      "INSERT INTO new_rate_limits SELECT "
-      "rate_limit_id,attribution_type,impression_id,impression_site,"
-      "impression_origin,conversion_destination,conversion_origin,"
-      "conversion_time,0,1 "
-      "FROM rate_limits";
-  if (!db->Execute(kPopulateNewRateLimitTableSql))
-    return false;
-
-  static constexpr char kDropOldRateLimitTableSql[] = "DROP TABLE rate_limits";
-  if (!db->Execute(kDropOldRateLimitTableSql))
-    return false;
-
-  static constexpr char kRenameRateLimitTableSql[] =
-      "ALTER TABLE new_rate_limits RENAME TO rate_limits";
-  if (!db->Execute(kRenameRateLimitTableSql))
-    return false;
-
-  // Create the pre-existing indices on the new table.
-
-  static constexpr char kRateLimitImpressionSiteTypeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS rate_limit_impression_site_type_idx "
-      "ON rate_limits(attribution_type,conversion_destination,"
-      "impression_site,conversion_time)";
-  if (!db->Execute(kRateLimitImpressionSiteTypeIndexSql))
-    return false;
-
-  // Add the attribution_type as a prefix of the index.
-  static constexpr char kRateLimitAttributionTypeConversionTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS "
-      "rate_limit_attribution_type_conversion_time_idx "
-      "ON rate_limits(attribution_type,conversion_time)";
-  if (!db->Execute(kRateLimitAttributionTypeConversionTimeIndexSql))
-    return false;
-
-  static constexpr char kRateLimitImpressionIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS rate_limit_impression_id_idx "
-      "ON rate_limits(impression_id)";
-  if (!db->Execute(kRateLimitImpressionIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(12);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion13(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;
-
-  // Create the new impressions table with impression_id `NOT NULL` and
-  // `AUTOINCREMENT`.
-  static constexpr char kNewImpressionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_impressions"
-      "(impression_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
-      "impression_data INTEGER 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,"
-      "priority INTEGER NOT NULL,"
-      "impression_site TEXT NOT NULL)";
-  if (!db->Execute(kNewImpressionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table.
-  static constexpr char kPopulateNewImpressionTableSql[] =
-      "INSERT INTO new_impressions SELECT "
-      "impression_id,impression_data,impression_origin,"
-      "conversion_origin,reporting_origin,impression_time,"
-      "expiry_time,num_conversions,active,conversion_destination,"
-      "source_type,attributed_truthfully,priority,impression_site "
-      "FROM impressions";
-  if (!db->Execute(kPopulateNewImpressionTableSql))
-    return false;
-
-  static constexpr char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
-  if (!db->Execute(kDropOldImpressionTableSql))
-    return false;
-
-  static constexpr char kRenameImpressionTableSql[] =
-      "ALTER TABLE new_impressions RENAME TO impressions";
-  if (!db->Execute(kRenameImpressionTableSql))
-    return false;
-
-  // Create the pre-existing impression table indices on the new table.
-
-  static constexpr char kConversionDestinationIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
-      "ON impressions(active,conversion_destination,reporting_origin)";
-  if (!db->Execute(kConversionDestinationIndexSql))
-    return false;
-
-  static constexpr char kImpressionExpiryIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
-      "ON impressions(expiry_time)";
-  if (!db->Execute(kImpressionExpiryIndexSql))
-    return false;
-
-  static constexpr char kImpressionOriginIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
-      "ON impressions(impression_origin)";
-  if (!db->Execute(kImpressionOriginIndexSql))
-    return false;
-
-  static constexpr char kEventSourceImpressionSiteIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS event_source_impression_site_idx "
-      "ON impressions(impression_site)"
-      "WHERE active = 1 AND num_conversions = 0 AND source_type = 1";
-  if (!db->Execute(kEventSourceImpressionSiteIndexSql))
-    return false;
-
-  // Create the new conversions table with conversion_id `NOT NULL` and
-  // `AUTOINCREMENT`.
-  static constexpr char kNewConversionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_conversions"
-      "(conversion_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
-      "impression_id INTEGER NOT NULL,"
-      "conversion_data INTEGER NOT NULL,"
-      "conversion_time INTEGER NOT NULL,"
-      "report_time INTEGER NOT NULL,"
-      "priority INTEGER NOT NULL)";
-  if (!db->Execute(kNewConversionTableSql))
-    return false;
-
-  // Transfer the existing rows to the new table.
-  static constexpr char kPopulateNewConversionTableSql[] =
-      "INSERT INTO new_conversions SELECT "
-      "conversion_id,impression_id,conversion_data,conversion_time,"
-      "report_time,priority "
-      "FROM conversions";
-  if (!db->Execute(kPopulateNewConversionTableSql))
-    return false;
-
-  static constexpr char kDropOldConversionTableSql[] = "DROP TABLE conversions";
-  if (!db->Execute(kDropOldConversionTableSql))
-    return false;
-
-  static constexpr char kRenameConversionTableSql[] =
-      "ALTER TABLE new_conversions RENAME TO conversions";
-  if (!db->Execute(kRenameConversionTableSql))
-    return false;
-
-  // Create the pre-existing conversion table indices on the new table.
-
-  static constexpr char kConversionReportTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_report_idx "
-      "ON conversions(report_time)";
-  if (!db->Execute(kConversionReportTimeIndexSql))
-    return false;
-
-  static constexpr char kConversionImpressionIdIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx "
-      "ON conversions(impression_id)";
-  if (!db->Execute(kConversionImpressionIdIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(13);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion14(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;
-
-  // Create the new conversions table with failed_send_attempts.
-  static constexpr char kNewConversionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_conversions"
-      "(conversion_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
-      "impression_id INTEGER NOT NULL,"
-      "conversion_data INTEGER NOT NULL,"
-      "conversion_time INTEGER NOT NULL,"
-      "report_time INTEGER NOT NULL,"
-      "priority INTEGER NOT NULL,"
-      "failed_send_attempts INTEGER NOT NULL)";
-  if (!db->Execute(kNewConversionTableSql))
-    return false;
-
-  // Transfer the existing conversions rows to the new table, using 0 for
-  // failed_send_attempts since we have no basis to say otherwise.
-  static constexpr char kPopulateNewConversionsSql[] =
-      "INSERT INTO new_conversions SELECT "
-      "conversion_id,impression_id,conversion_data,conversion_time,report_time,"
-      "priority,0 FROM conversions";
-  sql::Statement populate_new_conversions_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewConversionsSql));
-  if (!populate_new_conversions_statement.Run())
-    return false;
-
-  static constexpr char kDropOldConversionTableSql[] = "DROP TABLE conversions";
-  if (!db->Execute(kDropOldConversionTableSql))
-    return false;
-
-  static constexpr char kRenameConversionTableSql[] =
-      "ALTER TABLE new_conversions RENAME TO conversions";
-  if (!db->Execute(kRenameConversionTableSql))
-    return false;
-
-  // Create the pre-existing conversion table indices on the new table.
-
-  static constexpr char kConversionReportTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_report_idx "
-      "ON conversions(report_time)";
-  if (!db->Execute(kConversionReportTimeIndexSql))
-    return false;
-
-  static constexpr char kConversionImpressionIdIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx "
-      "ON conversions(impression_id)";
-  if (!db->Execute(kConversionImpressionIdIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(14);
-  return transaction.Commit();
-}
-
-bool MigrateToVersion15(sql::Database* db,
-                        sql::MetaTable* meta_table,
-                        AttributionStorage::Delegate* delegate) {
-  // Wrap each migration in its own transaction. See comment in
-  // |MigrateToVersion2|.
-  sql::Transaction transaction(db);
-  if (!transaction.Begin())
-    return false;
-
-  // Create the new conversions table with external_report_id.
-  static constexpr char kNewConversionTableSql[] =
-      "CREATE TABLE IF NOT EXISTS new_conversions"
-      "(conversion_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
-      "impression_id INTEGER NOT NULL,"
-      "conversion_data INTEGER NOT NULL,"
-      "conversion_time INTEGER NOT NULL,"
-      "report_time INTEGER NOT NULL,"
-      "priority INTEGER NOT NULL,"
-      "failed_send_attempts INTEGER NOT NULL,"
-      "external_report_id TEXT NOT NULL)";
-  if (!db->Execute(kNewConversionTableSql))
-    return false;
-
-  // Transfer the existing conversions rows to the new table, using the empty
-  // string for external_report_id, which we will update afterward.
-  static constexpr char kPopulateNewConversionsSql[] =
-      "INSERT INTO new_conversions SELECT "
-      "conversion_id,impression_id,conversion_data,conversion_time,report_time,"
-      "priority,failed_send_attempts,'' FROM conversions";
-  sql::Statement populate_new_conversions_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewConversionsSql));
-  if (!populate_new_conversions_statement.Run())
-    return false;
-
-  // Update each of the conversion rows to have a random external_report_id.
-  //
-  // We update a subset of rows at a time to avoid pulling the entire
-  // conversions table into memory.
-  std::vector<AttributionReport::EventLevelData::Id> conversion_ids =
-      GetConversionIds(db, AttributionReport::EventLevelData::Id(0));
-
-  static constexpr char kUpdateExternalReportIdSql[] =
-      "UPDATE new_conversions SET external_report_id = ? "
-      "WHERE conversion_id = ?";
-  sql::Statement update_statement(
-      db->GetCachedStatement(SQL_FROM_HERE, kUpdateExternalReportIdSql));
-
-  while (!conversion_ids.empty()) {
-    // Perform the column updates for each row we pulled into memory.
-    for (AttributionReport::EventLevelData::Id conversion_id : conversion_ids) {
-      update_statement.Reset(/*clear_bound_vars=*/true);
-
-      base::GUID external_report_id = delegate->NewReportID();
-      DCHECK(external_report_id.is_valid());
-      update_statement.BindString(0, external_report_id.AsLowercaseString());
-
-      update_statement.BindInt64(1, *conversion_id);
-
-      if (!update_statement.Run())
-        return false;
-    }
-
-    // Fetch the next batch of rows from the database.
-    conversion_ids =
-        GetConversionIds(db, NextConversionId(conversion_ids.back()));
-  }
-
-  static constexpr char kDropOldConversionTableSql[] = "DROP TABLE conversions";
-  if (!db->Execute(kDropOldConversionTableSql))
-    return false;
-
-  static constexpr char kRenameConversionTableSql[] =
-      "ALTER TABLE new_conversions RENAME TO conversions";
-  if (!db->Execute(kRenameConversionTableSql))
-    return false;
-
-  // Create the pre-existing conversion table indices on the new table.
-
-  static constexpr char kConversionReportTimeIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_report_idx "
-      "ON conversions(report_time)";
-  if (!db->Execute(kConversionReportTimeIndexSql))
-    return false;
-
-  static constexpr char kConversionImpressionIdIndexSql[] =
-      "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx "
-      "ON conversions(impression_id)";
-  if (!db->Execute(kConversionImpressionIdIndexSql))
-    return false;
-
-  meta_table->SetVersionNumber(15);
-  return transaction.Commit();
-}
-
-}  // namespace
-
-bool UpgradeAttributionStorageSqlSchema(
-    sql::Database* db,
-    sql::MetaTable* meta_table,
-    AttributionStorage::Delegate* delegate) {
+bool UpgradeAttributionStorageSqlSchema(sql::Database* db,
+                                        sql::MetaTable* meta_table) {
   DCHECK(db);
   DCHECK(meta_table);
-  DCHECK(delegate);
 
   base::ThreadTicks start_timestamp = base::ThreadTicks::Now();
 
-  if (meta_table->GetVersionNumber() == 1) {
-    if (!MigrateToVersion2(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 2) {
-    if (!MigrateToVersion3(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 3) {
-    if (!MigrateToVersion4(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 4) {
-    if (!MigrateToVersion5(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 5) {
-    if (!MigrateToVersion6(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 6) {
-    if (!MigrateToVersion7(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 7) {
-    if (!MigrateToVersion8(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 8) {
-    if (!MigrateToVersion9(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 9) {
-    if (!MigrateToVersion10(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 10) {
-    if (!MigrateToVersion11(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 11) {
-    if (!MigrateToVersion12(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 12) {
-    if (!MigrateToVersion13(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 13) {
-    if (!MigrateToVersion14(db, meta_table))
-      return false;
-  }
-  if (meta_table->GetVersionNumber() == 14) {
-    if (!MigrateToVersion15(db, meta_table, delegate))
-      return false;
-  }
-  // Add similar if () blocks for new versions here.
-
   base::UmaHistogramMediumTimes("Conversions.Storage.MigrationTime",
                                 base::ThreadTicks::Now() - start_timestamp);
   return true;
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_migrations.h b/content/browser/attribution_reporting/attribution_storage_sql_migrations.h
index eff1a4f..bbb5622a 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_migrations.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql_migrations.h
@@ -5,8 +5,6 @@
 #ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_SQL_MIGRATIONS_H_
 #define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_SQL_MIGRATIONS_H_
 
-#include "content/browser/attribution_reporting/attribution_storage.h"
-
 namespace sql {
 class Database;
 class MetaTable;
@@ -47,8 +45,7 @@
 // failure.
 [[nodiscard]] bool UpgradeAttributionStorageSqlSchema(
     sql::Database* db,
-    sql::MetaTable* meta_table,
-    AttributionStorage::Delegate* delegate);
+    sql::MetaTable* meta_table);
 
 }  // namespace content
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
index 2b13614..c32b1fa 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
@@ -134,698 +134,7 @@
   histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 0);
 }
 
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion1ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_1.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesTableExist("meta"));
-    ASSERT_FALSE(db.DoesColumnExist("impressions", "conversion_destination"));
-
-    sql::Statement s(
-        db.GetUniqueStatement("SELECT conversion_origin FROM impressions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ("https://sub.conversion.test", 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"));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesColumnExist("impressions", "conversion_destination"));
-
-    // Verify that data is preserved across the migration.
-    size_t rows = 0;
-    sql::test::CountTableRows(&db, "impressions", &rows);
-    EXPECT_EQ(1u, rows);
-
-    sql::Statement s(db.GetUniqueStatement(
-        "SELECT conversion_origin, conversion_destination FROM impressions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ("https://sub.conversion.test", s.ColumnString(0));
-    ASSERT_EQ("https://conversion.test", s.ColumnString(1));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion2ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_2.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesColumnExist("impressions", "source_type"));
-    ASSERT_FALSE(db.DoesColumnExist("impressions", "attributed_truthfully"));
-  }
-
-  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"));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesColumnExist("impressions", "source_type"));
-    EXPECT_TRUE(db.DoesColumnExist("impressions", "attributed_truthfully"));
-
-    // Verify that data is preserved across the migration.
-    size_t rows = 0;
-    sql::test::CountTableRows(&db, "impressions", &rows);
-    EXPECT_EQ(1u, rows);
-
-    sql::Statement s(db.GetUniqueStatement(
-        "SELECT source_type, attributed_truthfully FROM impressions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(0, s.ColumnInt(0));
-    ASSERT_EQ(true, s.ColumnBool(1));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion3ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_3.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesTableExist("rate_limits"));
-  }
-
-  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"));
-
-    // 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);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, 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_id FROM conversions ORDER BY conversion_id"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(2, s.ColumnInt(0));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(3, s.ColumnInt(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_id FROM conversions ORDER BY conversion_id"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(3, s.ColumnInt(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);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion5ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_5.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesColumnExist("impressions", "priority"));
-  }
-
-  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"));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesColumnExist("impressions", "priority"));
-
-    // Verify that data is preserved across the migration.
-    size_t rows = 0;
-    sql::test::CountTableRows(&db, "impressions", &rows);
-    EXPECT_EQ(1u, rows);
-
-    sql::Statement s(db.GetUniqueStatement(
-        "SELECT conversion_origin, priority FROM impressions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ("https://conversion.test", s.ColumnString(0));
-    ASSERT_EQ(0, s.ColumnInt64(1));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion6ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_6.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesColumnExist("impressions", "impression_site"));
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesColumnExist("impressions", "impression_site"));
-
-    // Verify that data is preserved across the migration.
-    size_t rows = 0;
-    sql::test::CountTableRows(&db, "impressions", &rows);
-    EXPECT_EQ(2u, rows);
-
-    sql::Statement s(db.GetUniqueStatement(
-        "SELECT impression_origin, impression_site FROM impressions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ("https://a.impression.test", s.ColumnString(0));
-    ASSERT_EQ("https://impression.test", s.ColumnString(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ("https://b.impression.test", s.ColumnString(0));
-    ASSERT_EQ("https://impression.test", s.ColumnString(1));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion7ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_7.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    sql::Statement impression_statement(db.GetUniqueStatement(
-        "SELECT impression_data FROM impressions ORDER BY impression_id"));
-    ASSERT_TRUE(impression_statement.Step());
-    ASSERT_EQ("18446744073709551615", impression_statement.ColumnString(0));
-    ASSERT_TRUE(impression_statement.Step());
-    ASSERT_EQ("invalid", impression_statement.ColumnString(0));
-    ASSERT_FALSE(impression_statement.Step());
-
-    sql::Statement conversion_statement(
-        db.GetUniqueStatement("SELECT conversion_data, impression_id FROM "
-                              "conversions ORDER BY conversion_id"));
-    ASSERT_TRUE(conversion_statement.Step());
-    ASSERT_EQ("234", conversion_statement.ColumnString(0));
-    ASSERT_EQ(29L, conversion_statement.ColumnInt64(1));
-    ASSERT_TRUE(conversion_statement.Step());
-    ASSERT_EQ("invalid", conversion_statement.ColumnString(0));
-    ASSERT_EQ(sql::ColumnType::kNull, conversion_statement.GetColumnType(1));
-    ASSERT_FALSE(conversion_statement.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"));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    sql::Statement impression_statement(db.GetUniqueStatement(
-        "SELECT impression_data FROM impressions ORDER BY impression_id"));
-    ASSERT_TRUE(impression_statement.Step());
-    ASSERT_EQ(18446744073709551615UL,
-              static_cast<uint64_t>(impression_statement.ColumnInt64(0)));
-    ASSERT_TRUE(impression_statement.Step());
-    ASSERT_EQ(0L, impression_statement.ColumnInt64(0));
-    ASSERT_FALSE(impression_statement.Step());
-
-    sql::Statement conversion_statement(
-        db.GetUniqueStatement("SELECT conversion_data, impression_id FROM "
-                              "conversions ORDER BY conversion_id"));
-    ASSERT_TRUE(conversion_statement.Step());
-    ASSERT_EQ(234L, conversion_statement.ColumnInt64(0));
-    ASSERT_EQ(29L, conversion_statement.ColumnInt64(1));
-    ASSERT_TRUE(conversion_statement.Step());
-    ASSERT_EQ(0L, conversion_statement.ColumnInt64(0));
-    ASSERT_EQ(0L, conversion_statement.ColumnInt64(1));
-    ASSERT_FALSE(conversion_statement.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion8ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_8.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesColumnExist("conversions", "priority"));
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesColumnExist("conversions", "priority"));
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion9ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_9.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_FALSE(db.DoesTableExist("dedup_keys"));
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    EXPECT_TRUE(db.DoesTableExist("dedup_keys"));
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion10ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_10.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    ASSERT_TRUE(db.DoesIndexExist("impression_site_idx"));
-    ASSERT_FALSE(db.DoesIndexExist("event_source_impression_site_idx"));
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    ASSERT_FALSE(db.DoesIndexExist("impression_site_idx"));
-    ASSERT_TRUE(db.DoesIndexExist("event_source_impression_site_idx"));
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion11ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_11.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-    ASSERT_FALSE(db.DoesColumnExist("rate_limits", "bucket"));
-    ASSERT_FALSE(db.DoesColumnExist("rate_limits", "value"));
-    ASSERT_FALSE(
-        db.DoesIndexExist("rate_limit_attribution_type_conversion_time_idx"));
-    ASSERT_TRUE(db.DoesIndexExist("rate_limit_conversion_time_idx"));
-
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM rate_limits"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(0, s.ColumnInt(1));
-    ASSERT_EQ(11, s.ColumnInt64(2));
-    ASSERT_EQ("https://a.example", s.ColumnString(3));
-    ASSERT_EQ("https://a.a.example", s.ColumnString(4));
-    ASSERT_EQ("https://b.example", s.ColumnString(5));
-    ASSERT_EQ("https://b.b.example", s.ColumnString(6));
-    ASSERT_EQ(7, s.ColumnInt64(7));
-    ASSERT_FALSE(s.Step());
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    ASSERT_TRUE(db.DoesColumnExist("rate_limits", "bucket"));
-    ASSERT_TRUE(db.DoesColumnExist("rate_limits", "value"));
-    ASSERT_TRUE(
-        db.DoesIndexExist("rate_limit_attribution_type_conversion_time_idx"));
-    ASSERT_FALSE(db.DoesIndexExist("rate_limit_conversion_time_idx"));
-
-    // Verify that data is preserved across the migration.
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM rate_limits"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(0, s.ColumnInt(1));
-    ASSERT_EQ(11, s.ColumnInt64(2));
-    ASSERT_EQ("https://a.example", s.ColumnString(3));
-    ASSERT_EQ("https://a.a.example", s.ColumnString(4));
-    ASSERT_EQ("https://b.example", s.ColumnString(5));
-    ASSERT_EQ("https://b.b.example", s.ColumnString(6));
-    ASSERT_EQ(7, s.ColumnInt64(7));
-    ASSERT_EQ(0, s.ColumnInt64(8));
-    ASSERT_EQ(1, s.ColumnInt64(9));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion12ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_12.sql"), DbPath());
-
-  auto check_data = [](sql::Database& db) {
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM impressions"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(2, s.ColumnInt(1));
-    ASSERT_EQ("a", s.ColumnString(2));
-    ASSERT_EQ("b", s.ColumnString(3));
-    ASSERT_EQ("c", s.ColumnString(4));
-    ASSERT_EQ(3, s.ColumnInt(5));
-    ASSERT_EQ(4, s.ColumnInt(6));
-    ASSERT_EQ(5, s.ColumnInt(7));
-    ASSERT_EQ(6, s.ColumnInt(8));
-    ASSERT_EQ("d", s.ColumnString(9));
-    ASSERT_EQ(7, s.ColumnInt(10));
-    ASSERT_EQ(8, s.ColumnInt(11));
-    ASSERT_EQ(9, s.ColumnInt(12));
-    ASSERT_EQ("e", s.ColumnString(13));
-    ASSERT_FALSE(s.Step());
-
-    sql::Statement t(db.GetUniqueStatement("SELECT * FROM conversions"));
-    ASSERT_TRUE(t.Step());
-    ASSERT_EQ(10, t.ColumnInt(0));
-    ASSERT_EQ(11, t.ColumnInt(1));
-    ASSERT_EQ(12, t.ColumnInt(2));
-    ASSERT_EQ(13, t.ColumnInt(3));
-    ASSERT_EQ(14, t.ColumnInt(4));
-    ASSERT_EQ(15, t.ColumnInt(5));
-    ASSERT_FALSE(t.Step());
-  };
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-    check_data(db);
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    check_data(db);
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion13ToCurrent) {
-  base::HistogramTester histograms;
-  LoadDatabase(FILE_PATH_LITERAL("version_13.sql"), DbPath());
-
-  // Verify pre-conditions.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-    ASSERT_FALSE(db.DoesColumnExist("conversions", "failed_send_attempts"));
-
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM conversions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(2, s.ColumnInt(1));
-    ASSERT_EQ(3, s.ColumnInt(2));
-    ASSERT_EQ(4, s.ColumnInt(3));
-    ASSERT_EQ(5, s.ColumnInt(4));
-    ASSERT_EQ(6, s.ColumnInt(5));
-    ASSERT_FALSE(s.Step());
-  }
-
-  MigrateDatabase();
-
-  // Verify schema is current.
-  {
-    sql::Database db;
-    ASSERT_TRUE(db.Open(DbPath()));
-
-    // Check version.
-    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
-
-    // Compare without quotes as sometimes migrations cause table names to be
-    // string literals.
-    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
-
-    // Check that the relevant schema changes are made.
-    ASSERT_TRUE(db.DoesColumnExist("conversions", "failed_send_attempts"));
-
-    // Verify that data is preserved across the migration.
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM conversions"));
-
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(2, s.ColumnInt(1));
-    ASSERT_EQ(3, s.ColumnInt(2));
-    ASSERT_EQ(4, s.ColumnInt(3));
-    ASSERT_EQ(5, s.ColumnInt(4));
-    ASSERT_EQ(6, s.ColumnInt(5));
-    ASSERT_EQ(0, s.ColumnInt(6));
-    ASSERT_FALSE(s.Step());
-  }
-
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
-}
-
-TEST_F(AttributionStorageSqlMigrationsTest, MigrateVersion14ToCurrent) {
+TEST_F(AttributionStorageSqlMigrationsTest, MigrateLatestDeprecatedToCurrent) {
   base::HistogramTester histograms;
   LoadDatabase(FILE_PATH_LITERAL("version_14.sql"), DbPath());
 
@@ -833,19 +142,11 @@
   {
     sql::Database db;
     ASSERT_TRUE(db.Open(DbPath()));
-    ASSERT_FALSE(db.DoesColumnExist("conversions", "external_report_id"));
 
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM conversions"));
+    sql::Statement s(db.GetUniqueStatement("SELECT COUNT(*) FROM conversions"));
 
     ASSERT_TRUE(s.Step());
     ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(2, s.ColumnInt(1));
-    ASSERT_EQ(3, s.ColumnInt(2));
-    ASSERT_EQ(4, s.ColumnInt(3));
-    ASSERT_EQ(5, s.ColumnInt(4));
-    ASSERT_EQ(6, s.ColumnInt(5));
-    ASSERT_EQ(7, s.ColumnInt(6));
-    ASSERT_FALSE(s.Step());
   }
 
   MigrateDatabase();
@@ -862,27 +163,16 @@
     // string literals.
     EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
 
-    // Check that the relevant schema changes are made.
-    ASSERT_TRUE(db.DoesColumnExist("conversions", "external_report_id"));
-
-    // Verify that data is preserved across the migration.
-    sql::Statement s(db.GetUniqueStatement("SELECT * FROM conversions"));
+    // Verify that data is not preserved across the migration.
+    sql::Statement s(db.GetUniqueStatement("SELECT COUNT(*) FROM conversions"));
 
     ASSERT_TRUE(s.Step());
-    ASSERT_EQ(1, s.ColumnInt(0));
-    ASSERT_EQ(2, s.ColumnInt(1));
-    ASSERT_EQ(3, s.ColumnInt(2));
-    ASSERT_EQ(4, s.ColumnInt(3));
-    ASSERT_EQ(5, s.ColumnInt(4));
-    ASSERT_EQ(6, s.ColumnInt(5));
-    ASSERT_EQ(7, s.ColumnInt(6));
-    ASSERT_TRUE(base::GUID::ParseLowercase(s.ColumnString(7)).is_valid());
-    ASSERT_FALSE(s.Step());
+    ASSERT_EQ(0, s.ColumnInt(0));
   }
 
-  // DB migration histograms should be recorded.
-  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
-  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
+  // DB creation histograms should be recorded.
+  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 1);
+  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 0);
 }
 
 }  // namespace content
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 9c238de..ededacc 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -298,10 +298,10 @@
     mojo::PendingReceiver<storage::mojom::QuotaInternalsHandler> receiver) {
   WebUI* web_ui = host->GetWebUI();
 
-  // Performs a safe downcast to the concrete QuotaInternals2UI
+  // Performs a safe downcast to the concrete QuotaInternalsUI
   // subclass.
-  QuotaInternals2UI* quota_internals_ui =
-      web_ui ? web_ui->GetController()->GetAs<QuotaInternals2UI>() : nullptr;
+  QuotaInternalsUI* quota_internals_ui =
+      web_ui ? web_ui->GetController()->GetAs<QuotaInternalsUI>() : nullptr;
 
   // This is expected to be called only for main frames and for the right WebUI
   // pages matching the same WebUI associated to the RenderFrameHost.
@@ -313,7 +313,7 @@
   }
 
   DCHECK_EQ(host->GetLastCommittedURL().host_piece(),
-            kChromeUIQuotaInternals2Host);
+            kChromeUIQuotaInternalsHost);
   DCHECK(host->GetLastCommittedURL().SchemeIs(kChromeUIScheme));
 
   static_cast<StoragePartitionImpl*>(host->GetStoragePartition())
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 49c3c4c..08c99ac 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -161,6 +161,102 @@
   return issue;
 }
 
+std::string RequestIdTokenStatusToProtocol(
+    blink::mojom::RequestIdTokenStatus status) {
+  using blink::mojom::RequestIdTokenStatus;
+  namespace FederatedAuthRequestIssueReasonEnum =
+      protocol::Audits::FederatedAuthRequestIssueReasonEnum;
+  switch (status) {
+    case RequestIdTokenStatus::kApprovalDeclined: {
+      return FederatedAuthRequestIssueReasonEnum::ApprovalDeclined;
+    }
+    case RequestIdTokenStatus::kErrorTooManyRequests: {
+      return FederatedAuthRequestIssueReasonEnum::TooManyRequests;
+    }
+    case RequestIdTokenStatus::kErrorFetchingWellKnownHttpNotFound: {
+      return FederatedAuthRequestIssueReasonEnum::WellKnownHttpNotFound;
+    }
+    case RequestIdTokenStatus::kErrorFetchingWellKnownNoResponse: {
+      return FederatedAuthRequestIssueReasonEnum::WellKnownNoResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingWellKnownInvalidResponse: {
+      return FederatedAuthRequestIssueReasonEnum::WellKnownInvalidResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingClientIdMetadataHttpNotFound: {
+      return FederatedAuthRequestIssueReasonEnum::ClientIdMetadataHttpNotFound;
+    }
+    case RequestIdTokenStatus::kErrorFetchingClientIdMetadataNoResponse: {
+      return FederatedAuthRequestIssueReasonEnum::ClientIdMetadataNoResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingClientIdMetadataInvalidResponse: {
+      return FederatedAuthRequestIssueReasonEnum::
+          ClientIdMetadataInvalidResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingSignin: {
+      return FederatedAuthRequestIssueReasonEnum::ErrorFetchingSignin;
+    }
+    case RequestIdTokenStatus::kErrorInvalidSigninResponse: {
+      return FederatedAuthRequestIssueReasonEnum::InvalidSigninResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingAccountsHttpNotFound: {
+      return FederatedAuthRequestIssueReasonEnum::AccountsHttpNotFound;
+    }
+    case RequestIdTokenStatus::kErrorFetchingAccountsNoResponse: {
+      return FederatedAuthRequestIssueReasonEnum::AccountsNoResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingAccountsInvalidResponse: {
+      return FederatedAuthRequestIssueReasonEnum::AccountsInvalidResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingIdTokenHttpNotFound: {
+      return FederatedAuthRequestIssueReasonEnum::IdTokenHttpNotFound;
+    }
+    case RequestIdTokenStatus::kErrorFetchingIdTokenNoResponse: {
+      return FederatedAuthRequestIssueReasonEnum::IdTokenNoResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingIdTokenInvalidResponse: {
+      return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidResponse;
+    }
+    case RequestIdTokenStatus::kErrorFetchingIdTokenInvalidRequest: {
+      return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidRequest;
+    }
+    case RequestIdTokenStatus::kErrorCanceled: {
+      return FederatedAuthRequestIssueReasonEnum::Canceled;
+    }
+    case RequestIdTokenStatus::kError: {
+      return FederatedAuthRequestIssueReasonEnum::ErrorIdToken;
+    }
+    case RequestIdTokenStatus::kSuccess: {
+      DCHECK(false);
+      return "";
+    }
+  }
+}
+
+std::unique_ptr<protocol::Audits::InspectorIssue>
+BuildFederatedAuthRequestIssue(
+    const blink::mojom::FederatedAuthRequestIssueDetailsPtr& issue_details) {
+  protocol::String type_string =
+      RequestIdTokenStatusToProtocol(issue_details->status);
+
+  auto federated_auth_request_details =
+      protocol::Audits::FederatedAuthRequestIssueDetails::Create()
+          .SetFederatedAuthRequestIssueReason(type_string)
+          .Build();
+
+  auto protocol_issue_details =
+      protocol::Audits::InspectorIssueDetails::Create()
+          .SetFederatedAuthRequestIssueDetails(
+              std::move(federated_auth_request_details))
+          .Build();
+
+  auto issue = protocol::Audits::InspectorIssue::Create()
+                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
+                                FederatedAuthRequestIssue)
+                   .SetDetails(std::move(protocol_issue_details))
+                   .Build();
+  return issue;
+}
+
 }  // namespace
 
 void OnResetNavigationRequest(NavigationRequest* navigation_request) {
@@ -1177,20 +1273,18 @@
 void BuildAndReportBrowserInitiatedIssue(
     RenderFrameHostImpl* frame,
     blink::mojom::InspectorIssueInfoPtr info) {
-  // This method does not support other types for now.
-  CHECK(info && info->details &&
-        (info->code == blink::mojom::InspectorIssueCode::kHeavyAdIssue &&
-             info->details->heavy_ad_issue_details ||
-         info->code ==
-                 blink::mojom::InspectorIssueCode::kTrustedWebActivityIssue &&
-             info->details->twa_issue_details));
-
   std::unique_ptr<protocol::Audits::InspectorIssue> issue;
   if (info->code ==
       blink::mojom::InspectorIssueCode::kTrustedWebActivityIssue) {
     issue = BuildTWAQualityIssue(info->details->twa_issue_details);
-  } else {
+  } else if (info->code == blink::mojom::InspectorIssueCode::kHeavyAdIssue) {
     issue = BuildHeavyAdIssue(info->details->heavy_ad_issue_details);
+  } else if (info->code ==
+             blink::mojom::InspectorIssueCode::kFederatedAuthRequestIssue) {
+    issue = BuildFederatedAuthRequestIssue(
+        info->details->federated_auth_request_details);
+  } else {
+    NOTREACHED() << "Unsupported type of browser-initiated issue";
   }
   ReportBrowserInitiatedIssue(frame, issue.get());
 }
diff --git a/content/browser/quota/quota_internals_browsertest.cc b/content/browser/quota/quota_internals_browsertest.cc
index bcf56f70..20270f6 100644
--- a/content/browser/quota/quota_internals_browsertest.cc
+++ b/content/browser/quota/quota_internals_browsertest.cc
@@ -18,7 +18,7 @@
 
 namespace {
 
-const char kQuotaInternalsUrl[] = "chrome://quota-internals-2/";
+const char kQuotaInternalsUrl[] = "chrome://quota-internals/";
 
 }  // namespace
 
diff --git a/content/browser/quota/quota_internals_ui.cc b/content/browser/quota/quota_internals_ui.cc
index bb34781..23b9447 100644
--- a/content/browser/quota/quota_internals_ui.cc
+++ b/content/browser/quota/quota_internals_ui.cc
@@ -15,14 +15,14 @@
 
 namespace content {
 
-QuotaInternals2UI::QuotaInternals2UI(WebUI* web_ui) : WebUIController(web_ui) {
+QuotaInternalsUI::QuotaInternalsUI(WebUI* web_ui) : WebUIController(web_ui) {
   WebUIDataSource* source =
-      WebUIDataSource::Create(kChromeUIQuotaInternals2Host);
+      WebUIDataSource::Create(kChromeUIQuotaInternalsHost);
 
   source->AddResourcePath("quota_internals.mojom-webui.js",
                           IDR_QUOTA_INTERNALS_MOJOM_JS);
   source->AddResourcePath("quota_internals.js", IDR_QUOTA_INTERNALS_JS);
-  source->AddResourcePath("quota-internals-2", IDR_QUOTA_INTERNALS_HTML);
+  source->AddResourcePath("quota-internals", IDR_QUOTA_INTERNALS_HTML);
   source->SetDefaultResource(IDR_QUOTA_INTERNALS_HTML);
 
   source->OverrideContentSecurityPolicy(
@@ -33,11 +33,11 @@
   WebUIDataSource::Add(web_contents->GetBrowserContext(), source);
 }
 
-WEB_UI_CONTROLLER_TYPE_IMPL(QuotaInternals2UI)
+WEB_UI_CONTROLLER_TYPE_IMPL(QuotaInternalsUI)
 
-QuotaInternals2UI::~QuotaInternals2UI() = default;
+QuotaInternalsUI::~QuotaInternalsUI() = default;
 
-void QuotaInternals2UI::WebUIRenderFrameCreated(
+void QuotaInternalsUI::WebUIRenderFrameCreated(
     RenderFrameHost* render_frame_host) {
   // Enable the JavaScript Mojo bindings in the renderer process, so the JS
   // code can call the Mojo APIs exposed by this WebUI.
diff --git a/content/browser/quota/quota_internals_ui.h b/content/browser/quota/quota_internals_ui.h
index 10644b18..8f9773f 100644
--- a/content/browser/quota/quota_internals_ui.h
+++ b/content/browser/quota/quota_internals_ui.h
@@ -14,13 +14,13 @@
 class RenderFrameHost;
 class WebUI;
 
-// WebUIController for the chrome://quota-internals-2 page.
-class CONTENT_EXPORT QuotaInternals2UI : public WebUIController {
+// WebUIController for the chrome://quota-internals page.
+class CONTENT_EXPORT QuotaInternalsUI : public WebUIController {
  public:
-  explicit QuotaInternals2UI(WebUI* web_ui);
-  QuotaInternals2UI(const QuotaInternals2UI& other) = delete;
-  QuotaInternals2UI& operator=(const QuotaInternals2UI& other) = delete;
-  ~QuotaInternals2UI() override;
+  explicit QuotaInternalsUI(WebUI* web_ui);
+  QuotaInternalsUI(const QuotaInternalsUI& other) = delete;
+  QuotaInternalsUI& operator=(const QuotaInternalsUI& other) = delete;
+  ~QuotaInternalsUI() override;
 
   // WebUIController overrides:
   void WebUIRenderFrameCreated(RenderFrameHost* render_frame_host) override;
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 20d613c4..99f3ba03 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -531,7 +531,7 @@
   // which updates `visibility_`, unless the host is hidden. Make sure no update
   // is needed.
   DCHECK(host_->is_hidden() || visibility_ == Visibility::VISIBLE);
-  OnShowWithPageVisibility(PageVisibilityState::kVisible);
+  OnShowWithPageVisibility(page_visibility);
 }
 
 void RenderWidgetHostViewAura::NotifyHostAndDelegateOnWasShown(
diff --git a/content/browser/renderer_host/render_widget_host_view_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
index cebb22b..687ebf7 100644
--- a/content/browser/renderer_host/render_widget_host_view_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/location.h"
 #include "base/memory/raw_ptr.h"
 #include "base/path_service.h"
@@ -13,7 +14,10 @@
 #include "base/strings/stringprintf.h"
 #include "base/task/current_thread.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/test_timeouts.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "cc/layers/surface_layer.h"
@@ -21,6 +25,7 @@
 #include "content/browser/renderer_host/dip_util.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/browser/renderer_host/visible_time_request_trigger.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/gpu_data_manager.h"
 #include "content/public/browser/render_frame_host.h"
@@ -39,6 +44,8 @@
 #include "net/base/filename_util.h"
 #include "net/dns/mock_host_resolver.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/page/page_visibility_state.mojom-shared.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/layout.h"
 #include "ui/display/display_switches.h"
@@ -1030,6 +1037,179 @@
     CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
     kTestCompositingModes);
 
+class RenderWidgetHostViewPresentationFeedbackBrowserTest
+    : public NoCompositingRenderWidgetHostViewBrowserTest {
+ public:
+  RenderWidgetHostViewPresentationFeedbackBrowserTest(
+      const RenderWidgetHostViewPresentationFeedbackBrowserTest&) = delete;
+  RenderWidgetHostViewPresentationFeedbackBrowserTest& operator=(
+      const RenderWidgetHostViewPresentationFeedbackBrowserTest&) = delete;
+
+ protected:
+  RenderWidgetHostViewPresentationFeedbackBrowserTest() = default;
+  ~RenderWidgetHostViewPresentationFeedbackBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    NoCompositingRenderWidgetHostViewBrowserTest::SetUpOnMainThread();
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+    ASSERT_TRUE(NavigateToURL(
+        shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
+
+    RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
+    ASSERT_TRUE(rwhvb);
+
+    // Start with the widget hidden.
+    rwhvb->Hide();
+
+    // Set a VisibleTimeRequest that will be sent the first time the widget
+    // becomes visible.
+    VisibleTimeRequestTrigger* request_trigger =
+        rwhvb->host()->GetVisibleTimeRequestTrigger();
+    ASSERT_TRUE(request_trigger);
+    request_trigger->UpdateRequest(base::TimeTicks::Now(),
+                                   /*destination_is_loaded=*/true,
+                                   /*show_reason_tab_switching=*/true,
+                                   /*show_reason_unoccluded=*/false,
+                                   /*show_reason_bfcache_restore=*/false);
+  }
+
+  enum class HistogramToExpect {
+    kTotalSwitchDuration,
+    kTotalIncompleteSwitchDuration,
+  };
+
+  ::testing::AssertionResult WaitForPresentationFeedback(
+      HistogramToExpect histogram_to_expect) {
+    // If TabSwitchMetrics2 is enabled, both Browser.Tabs.TotalSwitchDuration.*
+    // and Browser.Tabs.TotalSwitchDuration2.* will be logged.
+    const size_t expected_histogram_count =
+        base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2) ? 2
+                                                                          : 1;
+
+    // Expect one of Browser.Tabs.TotalSwitchDuration.* or
+    // Browser.Tabs.TotalIncompleteSwitchDuration.*.
+    //
+    // Browser.Tabs.TabSwitchResult.* is also logged with a result code, but the
+    // HistogramTest API makes it easier to count the number of samples for any
+    // suffix of TotalSwitchDuration than to check the exact histogram values
+    // for each possible suffix of TabSwitchResult.
+    const char* expected_prefix;
+    const char* unexpected_prefix;
+    switch (histogram_to_expect) {
+      case HistogramToExpect::kTotalSwitchDuration:
+        expected_prefix = "Browser.Tabs.TotalSwitchDuration";
+        unexpected_prefix = "Browser.Tabs.TotalIncompleteSwitchDuration";
+        break;
+      case HistogramToExpect::kTotalIncompleteSwitchDuration:
+        expected_prefix = "Browser.Tabs.TotalIncompleteSwitchDuration";
+        unexpected_prefix = "Browser.Tabs.TotalSwitchDuration";
+        break;
+    }
+
+    // Wait for the expected histograms (only) to be logged.
+    const base::TimeTicks start_time = base::TimeTicks::Now();
+    while (base::TimeTicks::Now() - start_time <
+           TestTimeouts::action_timeout()) {
+      GiveItSomeTime();
+
+      if (!histogram_tester_.GetTotalCountsForPrefix(unexpected_prefix)
+               .empty()) {
+        return ::testing::AssertionFailure()
+               << "Unexpected histogram " << unexpected_prefix
+               << ". All histograms: "
+               << ::testing::PrintToString(
+                      histogram_tester_.GetTotalCountsForPrefix(
+                          "Browser.Tabs."));
+      }
+      if (histogram_tester_.GetTotalCountsForPrefix(expected_prefix).size() ==
+          expected_histogram_count) {
+        return ::testing::AssertionSuccess();
+      }
+    }
+
+    return ::testing::AssertionFailure()
+           << "Timed out waiting for " << expected_prefix
+           << ". All histograms: "
+           << ::testing::PrintToString(
+                  histogram_tester_.GetTotalCountsForPrefix("Browser.Tabs."));
+  }
+
+  base::HistogramTester histogram_tester_;
+};
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
+                       Show) {
+  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
+  EXPECT_TRUE(
+      WaitForPresentationFeedback(HistogramToExpect::kTotalSwitchDuration));
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
+                       ShowThenHide) {
+  // Browser.Tabs.TotalIncompleteSwitchDuration.* is logged when the widget
+  // is hidden before presenting a frame.
+  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
+  GetRenderWidgetHostView()->Hide();
+  EXPECT_TRUE(WaitForPresentationFeedback(
+      HistogramToExpect::kTotalIncompleteSwitchDuration));
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
+                       HiddenButPainting) {
+  if (!base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2)) {
+    GTEST_SKIP() << "Visibility changes with a hidden capturer are only "
+                    "handled when TabSwitchMetrics2 is enabled";
+  }
+
+  // Browser.Tabs.* is not logged if the page becomes "visible" due to a hidden
+  // capturer.
+  GetRenderWidgetHostView()->ShowWithVisibility(
+      PageVisibilityState::kHiddenButPainting);
+
+  // The full action_timeout is excessively long for the expected path.
+  const base::TimeTicks start_time = base::TimeTicks::Now();
+  while (base::TimeTicks::Now() - start_time < base::Seconds(1)) {
+    GiveItSomeTime();
+    ASSERT_TRUE(
+        histogram_tester_.GetTotalCountsForPrefix("Browser.Tabs.").empty())
+        << "Unexpected histogram Browser.Tabs. All histograms: "
+        << ::testing::PrintToString(
+               histogram_tester_.GetTotalCountsForPrefix("Browser.Tabs."));
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
+                       ShowWhileCapturing) {
+  if (!base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2)) {
+    GTEST_SKIP() << "Visibility changes with a hidden capturer are only "
+                    "handled when TabSwitchMetrics2 is enabled";
+  }
+
+  // Frame is captured and then becomes visible.
+  GetRenderWidgetHostView()->ShowWithVisibility(
+      PageVisibilityState::kHiddenButPainting);
+  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
+  EXPECT_TRUE(
+      WaitForPresentationFeedback(HistogramToExpect::kTotalSwitchDuration));
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
+                       HideWhileCapturing) {
+  if (!base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2)) {
+    GTEST_SKIP() << "Visibility changes with a hidden capturer are only "
+                    "handled when TabSwitchMetrics2 is enabled";
+  }
+
+  // Capture starts and frame becomes "hidden" before a render frame is
+  // presented.
+  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
+  GetRenderWidgetHostView()->ShowWithVisibility(
+      PageVisibilityState::kHiddenButPainting);
+  EXPECT_TRUE(WaitForPresentationFeedback(
+      HistogramToExpect::kTotalIncompleteSwitchDuration));
+}
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc
index bfcb10a..f112293 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc
@@ -413,6 +413,11 @@
   };
   ASSERT_TRUE(trigger_subframe_tab_switch());
 
+  // If TabSwitchMetrics2 is enabled, both Browser.Tabs.TotalSwitchDuration.*
+  // and Browser.Tabs.TotalSwitchDuration2.* will be logged.
+  const size_t expected_histogram_count =
+      base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2) ? 2 : 1;
+
   bool got_incomplete_tab_switch = false;
   const base::TimeTicks start_time = base::TimeTicks::Now();
   do {
@@ -454,7 +459,7 @@
         histogram_tester
                 .GetTotalCountsForPrefix(
                     "Browser.Tabs.TotalIncompleteSwitchDuration")
-                .size() == 1) {
+                .size() == expected_histogram_count) {
       LOG(ERROR) << "Incomplete tab switch - try again.";
       got_incomplete_tab_switch = true;
       ASSERT_TRUE(trigger_subframe_tab_switch());
@@ -464,7 +469,7 @@
     // single TotalSwitchDuration histogram to be logged.
   } while (histogram_tester
                .GetTotalCountsForPrefix("Browser.Tabs.TotalSwitchDuration")
-               .size() != 1);
+               .size() != expected_histogram_count);
 }
 
 // Auto-resize is only implemented for Ash and GuestViews. So we need to inject
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index a9401841..1161566a 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -4894,6 +4894,7 @@
   EXPECT_TRUE(browser_td >= renderer_td);
 }
 
+#if !BUILDFLAG(IS_ANDROID)
 class WebContentsImplBrowserTestWindowControlsOverlay
     : public WebContentsImplBrowserTest {
  public:
@@ -5059,7 +5060,6 @@
   EXPECT_EQ(bounding_client_rect.height(), EvalJs(web_contents, "rect.height"));
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay,
                        ValidatePageScaleChangesInfoAndFiresEvent) {
   auto* web_contents = shell()->web_contents();
@@ -5104,7 +5104,6 @@
   EXPECT_EQ(scaled_rect.height(), EvalJs(web_contents, "rect.height"));
   ValidateWindowsControlOverlayState(web_contents, scaled_rect, 60);
 }
-#endif
 
 class WebContentsImplBrowserTestWindowControlsOverlayNonOneDeviceScaleFactor
     : public WebContentsImplBrowserTestWindowControlsOverlay {
@@ -5146,6 +5145,7 @@
   WaitForWindowControlsOverlayUpdate(web_contents, bounding_client_rect);
   ValidateWindowsControlOverlayState(web_contents, bounding_client_rect, 70);
 }
+#endif
 
 class RenderFrameCreatedObserver : public WebContentsObserver {
  public:
diff --git a/content/browser/webid/fedcm_metrics.cc b/content/browser/webid/fedcm_metrics.cc
index c587c450..8daa1c2 100644
--- a/content/browser/webid/fedcm_metrics.cc
+++ b/content/browser/webid/fedcm_metrics.cc
@@ -10,32 +10,68 @@
 
 namespace content {
 
-void RecordShowAccountsDialogTime(base::TimeDelta duration) {
+void RecordShowAccountsDialogTime(base::TimeDelta duration,
+                                  ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder.SetTiming_ShowAccountsDialog(
+      ukm::GetExponentialBucketMinForUserTiming(duration.InMilliseconds()));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_MEDIUM_TIMES("Blink.FedCm.Timing.ShowAccountsDialog", duration);
 }
 
-void RecordContinueOnDialogTime(base::TimeDelta duration) {
+void RecordContinueOnDialogTime(base::TimeDelta duration,
+                                ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder.SetTiming_ContinueOnDialog(
+      ukm::GetExponentialBucketMinForUserTiming(duration.InMilliseconds()));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_MEDIUM_TIMES("Blink.FedCm.Timing.ContinueOnDialog", duration);
 }
 
-void RecordCancelOnDialogTime(base::TimeDelta duration) {
+void RecordCancelOnDialogTime(base::TimeDelta duration,
+                              ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder.SetTiming_CancelOnDialog(
+      ukm::GetExponentialBucketMinForUserTiming(duration.InMilliseconds()));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_MEDIUM_TIMES("Blink.FedCm.Timing.CancelOnDialog", duration);
 }
 
 void RecordIdTokenResponseAndTurnaroundTime(
     base::TimeDelta id_token_response_time,
-    base::TimeDelta turnaround_time) {
+    base::TimeDelta turnaround_time,
+    ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder
+      .SetTiming_IdTokenResponse(ukm::GetExponentialBucketMinForUserTiming(
+          id_token_response_time.InMilliseconds()))
+      .SetTiming_TurnaroundTime(ukm::GetExponentialBucketMinForUserTiming(
+          turnaround_time.InMilliseconds()));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_MEDIUM_TIMES("Blink.FedCm.Timing.IdTokenResponse",
                              id_token_response_time);
   UMA_HISTOGRAM_MEDIUM_TIMES("Blink.FedCm.Timing.TurnaroundTime",
                              turnaround_time);
 }
 
-void RecordRequestIdTokenStatus(FedCmRequestIdTokenStatus status) {
+void RecordRequestIdTokenStatus(FedCmRequestIdTokenStatus status,
+                                ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder.SetStatus_RequestIdToken(static_cast<int>(status));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_ENUMERATION("Blink.FedCm.Status.RequestIdToken", status);
 }
 
-void RecordRevokeStatus(FedCmRevokeStatus status) {
+void RecordRevokeStatus(FedCmRevokeStatus status, ukm::SourceId source_id) {
+  ukm::builders::Blink_FedCm builder(source_id);
+  builder.SetStatus_Revoke(static_cast<int>(status));
+  builder.Record(ukm::UkmRecorder::Get());
+
   UMA_HISTOGRAM_ENUMERATION("Blink.FedCm.Status.Revoke", status);
 }
 
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index a36cf58..cb31426b 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -5,6 +5,8 @@
 #ifndef CONTENT_BROWSER_WEBID_FEDCM_METRICS_H_
 #define CONTENT_BROWSER_WEBID_FEDCM_METRICS_H_
 
+#include "services/metrics/public/cpp/ukm_builders.h"
+
 namespace base {
 class TimeDelta;
 }
@@ -58,32 +60,37 @@
 
 // Records the time from when a call to the API was made to when the accounts
 // dialog is shown.
-void RecordShowAccountsDialogTime(base::TimeDelta duration);
+void RecordShowAccountsDialogTime(base::TimeDelta duration,
+                                  ukm::SourceId source_id);
 
 // Records the time from when the accounts dialog is shown to when the user
 // presses the Continue button.
-void RecordContinueOnDialogTime(base::TimeDelta duration);
+void RecordContinueOnDialogTime(base::TimeDelta duration,
+                                ukm::SourceId source_id);
 
 // Records the time from when the accounts dialog is shown to when the user
 // closes the dialog without selecting any account.
-void RecordCancelOnDialogTime(base::TimeDelta duration);
+void RecordCancelOnDialogTime(base::TimeDelta duration,
+                              ukm::SourceId source_id);
 
 // Records the time from when the user presses the Continue button to when the
 // idtoken response is received. Also records the overall time from when the API
 // is called to when the idtoken response is received.
 void RecordIdTokenResponseAndTurnaroundTime(
     base::TimeDelta id_token_response_time,
-    base::TimeDelta turnaround_time);
+    base::TimeDelta turnaround_time,
+    ukm::SourceId source_id);
 
 // Records the status of the |RequestIdToken| call.
 // TODO(yigu): Call this function from |CompleteRequest| once the mojom side
 // |RequestIdTokenStatus| is cleaned up.
-void RecordRequestIdTokenStatus(FedCmRequestIdTokenStatus status);
+void RecordRequestIdTokenStatus(FedCmRequestIdTokenStatus status,
+                                ukm::SourceId source_id);
 
 // Records the status of the |Revoke| call.
 // TODO(yigu): Call this function from |CompleteRevokeRequest| once the mojom
 // side |RevokeStatus| is cleaned up.
-void RecordRevokeStatus(FedCmRevokeStatus status);
+void RecordRevokeStatus(FedCmRevokeStatus status, ukm::SourceId source_id);
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_WEBID_FEDCM_METRICS_H_
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 4035f9d..984effc 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
 #include "ui/accessibility/ax_mode.h"
 #include "url/url_constants.h"
 
@@ -96,12 +97,14 @@
   if (auth_request_callback_) {
     DCHECK(!revoke_callback_);
     DCHECK(!logout_callback_);
-    RecordRequestIdTokenStatus(IdTokenStatus::kUnhandledRequest);
+    RecordRequestIdTokenStatus(IdTokenStatus::kUnhandledRequest,
+                               render_frame_host_->GetPageUkmSourceId());
   }
   if (revoke_callback_) {
     DCHECK(!auth_request_callback_);
     DCHECK(!logout_callback_);
-    RecordRevokeStatus(RevokeStatusForMetrics::kUnhandledRequest);
+    RecordRevokeStatus(RevokeStatusForMetrics::kUnhandledRequest,
+                       render_frame_host_->GetPageUkmSourceId());
   }
   CompleteRequest(RequestIdTokenStatus::kError, "");
 }
@@ -114,7 +117,8 @@
     bool prefer_auto_sign_in,
     blink::mojom::FederatedAuthRequest::RequestIdTokenCallback callback) {
   if (HasPendingRequest()) {
-    RecordRequestIdTokenStatus(IdTokenStatus::kTooManyRequests);
+    RecordRequestIdTokenStatus(IdTokenStatus::kTooManyRequests,
+                               render_frame_host_->GetPageUkmSourceId());
     std::move(callback).Run(RequestIdTokenStatus::kErrorTooManyRequests, "");
     return;
   }
@@ -129,7 +133,8 @@
 
   network_manager_ = CreateNetworkManager(provider);
   if (!network_manager_) {
-    RecordRequestIdTokenStatus(IdTokenStatus::kNoNetworkManager);
+    RecordRequestIdTokenStatus(IdTokenStatus::kNoNetworkManager,
+                               render_frame_host_->GetPageUkmSourceId());
     CompleteRequest(RequestIdTokenStatus::kError, "");
     return;
   }
@@ -168,7 +173,8 @@
 
   // Dialog will be hidden by the destructor for request_dialog_controller_,
   // triggered by CompleteRequest.
-  RecordRequestIdTokenStatus(IdTokenStatus::kAborted);
+  RecordRequestIdTokenStatus(IdTokenStatus::kAborted,
+                             render_frame_host_->GetPageUkmSourceId());
   CompleteRequest(RequestIdTokenStatus::kErrorCanceled, "");
 }
 
@@ -178,7 +184,8 @@
     const std::string& account_id,
     blink::mojom::FederatedAuthRequest::RevokeCallback callback) {
   if (HasPendingRequest()) {
-    RecordRevokeStatus(RevokeStatusForMetrics::kTooManyRequests);
+    RecordRevokeStatus(RevokeStatusForMetrics::kTooManyRequests,
+                       render_frame_host_->GetPageUkmSourceId());
     std::move(callback).Run(RevokeStatus::kError);
     return;
   }
@@ -190,7 +197,8 @@
 
   network_manager_ = CreateNetworkManager(provider);
   if (!network_manager_) {
-    RecordRevokeStatus(RevokeStatusForMetrics::kNoNetworkManager);
+    RecordRevokeStatus(RevokeStatusForMetrics::kNoNetworkManager,
+                       render_frame_host_->GetPageUkmSourceId());
     CompleteRevokeRequest(RevokeStatus::kError);
     return;
   }
@@ -198,7 +206,8 @@
   if (!GetRequestPermissionContext() ||
       !GetRequestPermissionContext()->HasRequestPermission(
           origin_, url::Origin::Create(provider_))) {
-    RecordRevokeStatus(RevokeStatusForMetrics::kNoAccountToRevoke);
+    RecordRevokeStatus(RevokeStatusForMetrics::kNoAccountToRevoke,
+                       render_frame_host_->GetPageUkmSourceId());
     CompleteRevokeRequest(RevokeStatus::kError);
     return;
   }
@@ -274,19 +283,22 @@
     IdpNetworkRequestManager::Endpoints endpoints) {
   switch (status) {
     case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownHttpNotFound);
+      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownHttpNotFound,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingWellKnownHttpNotFound,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownNoResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownNoResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingWellKnownNoResponse,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(
           RequestIdTokenStatus::kErrorFetchingWellKnownInvalidResponse, "");
       return;
@@ -311,7 +323,8 @@
       // For Mediated mode we require accounts, token and client ID endpoints.
       if (endpoints_.token.is_empty() || endpoints_.accounts.is_empty() ||
           endpoints_.client_id_metadata.is_empty()) {
-        RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse);
+        RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse,
+                                   render_frame_host_->GetPageUkmSourceId());
         CompleteRequest(
             RequestIdTokenStatus::kErrorFetchingWellKnownInvalidResponse, "");
         return;
@@ -321,7 +334,8 @@
       if (!IdpUrlIsValid(endpoints_.token) ||
           !IdpUrlIsValid(endpoints_.accounts) ||
           !IdpUrlIsValid(endpoints_.client_id_metadata)) {
-        RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse);
+        RecordRequestIdTokenStatus(IdTokenStatus::kWellKnownInvalidResponse,
+                                   render_frame_host_->GetPageUkmSourceId());
         CompleteRequest(
             RequestIdTokenStatus::kErrorFetchingWellKnownInvalidResponse, "");
         return;
@@ -362,17 +376,20 @@
     IdpNetworkRequestManager::Endpoints endpoints) {
   switch (status) {
     case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
-      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownHttpNotFound);
+      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownHttpNotFound,
+                         render_frame_host_->GetPageUkmSourceId());
       CompleteRevokeRequest(RevokeStatus::kError);
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
-      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownNoResponse);
+      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownNoResponse,
+                         render_frame_host_->GetPageUkmSourceId());
       CompleteRevokeRequest(RevokeStatus::kError);
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
-      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownInvalidResponse);
+      RecordRevokeStatus(RevokeStatusForMetrics::kWellKnownInvalidResponse,
+                         render_frame_host_->GetPageUkmSourceId());
       CompleteRevokeRequest(RevokeStatus::kError);
       return;
     }
@@ -389,7 +406,8 @@
   // TODO(kenrb): This has to be same-origin with the provider.
   // https://crbug.com/1141125
   if (!IdpUrlIsValid(revoke_url)) {
-    RecordRevokeStatus(RevokeStatusForMetrics::kRevokeUrlIsCrossOrigin);
+    RecordRevokeStatus(RevokeStatusForMetrics::kRevokeUrlIsCrossOrigin,
+                       render_frame_host_->GetPageUkmSourceId());
     CompleteRevokeRequest(RevokeStatus::kError);
     return;
   }
@@ -420,9 +438,11 @@
       GetActiveSessionPermissionContext()->RevokeActiveSession(
           origin_, idp_origin, account_id_);
     }
-    RecordRevokeStatus(RevokeStatusForMetrics::kSuccess);
+    RecordRevokeStatus(RevokeStatusForMetrics::kSuccess,
+                       render_frame_host_->GetPageUkmSourceId());
   } else {
-    RecordRevokeStatus(RevokeStatusForMetrics::kRevocationFailedOnServer);
+    RecordRevokeStatus(RevokeStatusForMetrics::kRevocationFailedOnServer,
+                       render_frame_host_->GetPageUkmSourceId());
   }
   CompleteRevokeRequest(status);
 }
@@ -605,19 +625,22 @@
     IdentityProviderMetadata idp_metadata) {
   switch (status) {
     case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsHttpNotFound);
+      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsHttpNotFound,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingAccountsHttpNotFound,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsNoResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsNoResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingAccountsNoResponse,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsInvalidResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kAccountsInvalidResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(
           RequestIdTokenStatus::kErrorFetchingAccountsInvalidResponse, "");
       return;
@@ -655,7 +678,8 @@
       ClientIdData data{GURL(client_id_metadata_.terms_of_service_url),
                         GURL(client_id_metadata_.privacy_policy_url)};
       show_accounts_dialog_time_ = base::TimeTicks::Now();
-      RecordShowAccountsDialogTime(show_accounts_dialog_time_ - start_time_);
+      RecordShowAccountsDialogTime(show_accounts_dialog_time_ - start_time_,
+                                   render_frame_host_->GetPageUkmSourceId());
 
       request_dialog_controller_->ShowAccountsDialog(
           rp_web_contents, idp_web_contents_.get(), provider_, accounts,
@@ -676,8 +700,10 @@
   // This could happen if user didn't select any accounts.
   if (account_id.empty()) {
     base::TimeTicks dismiss_dialog_time = base::TimeTicks::Now();
-    RecordCancelOnDialogTime(dismiss_dialog_time - show_accounts_dialog_time_);
-    RecordRequestIdTokenStatus(IdTokenStatus::kNotSelectAccount);
+    RecordCancelOnDialogTime(dismiss_dialog_time - show_accounts_dialog_time_,
+                             render_frame_host_->GetPageUkmSourceId());
+    RecordRequestIdTokenStatus(IdTokenStatus::kNotSelectAccount,
+                               render_frame_host_->GetPageUkmSourceId());
     CompleteRequest(RequestIdTokenStatus::kError, "");
     return;
   }
@@ -691,7 +717,8 @@
 
   account_id_ = account_id;
   select_account_time_ = base::TimeTicks::Now();
-  RecordContinueOnDialogTime(select_account_time_ - show_accounts_dialog_time_);
+  RecordContinueOnDialogTime(select_account_time_ - show_accounts_dialog_time_,
+                             render_frame_host_->GetPageUkmSourceId());
 
   network_manager_->SendTokenRequest(
       endpoints_.token, account_id_,
@@ -708,7 +735,7 @@
   // takes a long time due to latency etc.. In case that the fetching process is
   // fast, we still want to show the "Verify" sheet for at least
   // |kIdTokenRequestDelay| seconds for better UX.
-  base::TimeTicks id_token_response_time_ = base::TimeTicks::Now();
+  id_token_response_time_ = base::TimeTicks::Now();
   base::TimeDelta fetch_time = id_token_response_time_ - select_account_time_;
   if (fetch_time >= kIdTokenRequestDelay) {
     CompleteIdTokenRequest(status, id_token);
@@ -728,25 +755,29 @@
   DCHECK(!start_time_.is_null());
   switch (status) {
     case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenHttpNotFound);
+      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenHttpNotFound,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingIdTokenHttpNotFound,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenNoResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenNoResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingIdTokenNoResponse,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidRequest);
+      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidRequest,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kErrorFetchingIdTokenInvalidRequest,
                       "");
       return;
     }
     case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidResponse);
+      RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidResponse,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(
           RequestIdTokenStatus::kErrorFetchingIdTokenInvalidResponse, "");
       return;
@@ -778,9 +809,11 @@
 
       RecordIdTokenResponseAndTurnaroundTime(
           id_token_response_time_ - select_account_time_,
-          id_token_response_time_ - start_time_);
+          id_token_response_time_ - start_time_,
+          render_frame_host_->GetPageUkmSourceId());
       id_token_ = id_token;
-      RecordRequestIdTokenStatus(IdTokenStatus::kSuccess);
+      RecordRequestIdTokenStatus(IdTokenStatus::kSuccess,
+                                 render_frame_host_->GetPageUkmSourceId());
       CompleteRequest(RequestIdTokenStatus::kSuccess, id_token_);
       return;
     }
@@ -844,6 +877,17 @@
     const std::string& id_token) {
   DCHECK(status == RequestIdTokenStatus::kSuccess || id_token.empty());
 
+  if (status != RequestIdTokenStatus::kSuccess) {
+    // It would be possible to add this inspector issue on the renderer, which
+    // will receive the callback. However, it is preferable to do so on the
+    // browser because this is closer to the source, which means adding
+    // additional metadata is easier. In addition, in the future we may only
+    // need to pass a small amount of information to the renderer in the case of
+    // an error, so it would be cleaner to do this by reporting the inspector
+    // issue from the browser.
+    AddInspectorIssue(status);
+  }
+
   CleanUp();
 
   if (auth_request_callback_)
@@ -863,6 +907,20 @@
   id_token_response_time_ = base::TimeTicks();
 }
 
+void FederatedAuthRequestImpl::AddInspectorIssue(
+    blink::mojom::RequestIdTokenStatus status) {
+  DCHECK_NE(status, blink::mojom::RequestIdTokenStatus::kSuccess);
+  auto details = blink::mojom::InspectorIssueDetails::New();
+  auto federated_auth_request_details =
+      blink::mojom::FederatedAuthRequestIssueDetails::New(status);
+  details->federated_auth_request_details =
+      std::move(federated_auth_request_details);
+  render_frame_host_->ReportInspectorIssue(
+      blink::mojom::InspectorIssueInfo::New(
+          blink::mojom::InspectorIssueCode::kFederatedAuthRequestIssue,
+          std::move(details)));
+}
+
 void FederatedAuthRequestImpl::CompleteLogoutRequest(
     blink::mojom::LogoutStatus status) {
   network_manager_.reset();
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 41c2831..7ebd1f81c 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -119,6 +119,10 @@
   FederatedIdentitySharingPermissionContextDelegate*
   GetSharingPermissionContext();
 
+  // Creates an inspector issue related to a federated authentication request to
+  // the Issues panel in DevTools.
+  void AddInspectorIssue(blink::mojom::RequestIdTokenStatus status);
+
   const raw_ptr<RenderFrameHost> render_frame_host_ = nullptr;
   const url::Origin origin_;
 
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index df3e5bf..4531a2aa8 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/webid/fedcm_metrics.h"
 #include "content/browser/webid/federated_auth_request_service.h"
 #include "content/browser/webid/id_token_request_callback_data.h"
@@ -25,8 +26,10 @@
 #include "content/public/browser/browser_accessibility_state.h"
 #include "content/public/browser/identity_request_dialog_controller.h"
 #include "content/public/common/content_features.h"
-#include "content/public/test/test_renderer_host.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -40,6 +43,7 @@
 using blink::mojom::RequestIdTokenStatus;
 using blink::mojom::RequestMode;
 using blink::mojom::RevokeStatus;
+using Entry = ukm::builders::Blink_FedCm;
 using FetchStatus = content::IdpNetworkRequestManager::FetchStatus;
 using LogoutResponse = content::IdpNetworkRequestManager::LogoutResponse;
 using SigninResponse = content::IdpNetworkRequestManager::SigninResponse;
@@ -459,11 +463,11 @@
 
 }  // namespace
 
-class FederatedAuthRequestImplTest : public RenderViewHostTestHarness {
+class FederatedAuthRequestImplTest : public RenderViewHostImplTestHarness {
  protected:
-  FederatedAuthRequestImplTest()
-      : RenderViewHostTestHarness(
-            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+  FederatedAuthRequestImplTest() {
+    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
+  }
   ~FederatedAuthRequestImplTest() override = default;
 
   FederatedAuthRequestImpl& CreateAuthRequest(const GURL& provider) {
@@ -712,6 +716,70 @@
     return mock_dialog_controller_;
   }
 
+  ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); }
+
+  void ExpectRequestIdTokenStatusUKM(IdTokenStatus status) {
+    auto entries = ukm_recorder()->GetEntriesByName(Entry::kEntryName);
+
+    if (entries.empty())
+      FAIL() << "No RequestIdTokenStatus was recorded";
+
+    // There are multiple types of metrics under the same FedCM UKM. We need to
+    // make sure that the metric only includes the expected one.
+    for (const auto* const entry : entries) {
+      const int64_t* metric =
+          ukm_recorder()->GetEntryMetric(entry, "Status_RequestIdToken");
+      if (metric && *metric != static_cast<int>(status))
+        FAIL() << "Unexpected status was recorded";
+    }
+
+    SUCCEED();
+  }
+
+  void ExpectRevokeStatusUKM(RevokeStatusForMetrics status) {
+    auto entries = ukm_recorder()->GetEntriesByName(Entry::kEntryName);
+
+    if (entries.empty())
+      FAIL() << "No RevokeStatus was recorded";
+
+    // There are multiple types of metrics under the same FedCM UKM. We need to
+    // make sure that the metric only includes the expected one.
+    for (const auto* const entry : entries) {
+      const int64_t* metric =
+          ukm_recorder()->GetEntryMetric(entry, "Status_Revoke");
+      if (metric && *metric != static_cast<int>(status))
+        FAIL() << "Unexpected status was recorded";
+    }
+
+    SUCCEED();
+  }
+
+  void ExpectTimingUKM(const std::string& metric_name) {
+    auto entries = ukm_recorder()->GetEntriesByName(Entry::kEntryName);
+
+    ASSERT_FALSE(entries.empty());
+
+    for (const auto* const entry : entries) {
+      if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
+        SUCCEED();
+        return;
+      }
+    }
+    FAIL() << "Expected UKM was not recorded";
+  }
+
+  void ExpectNoTimingUKM(const std::string& metric_name) {
+    auto entries = ukm_recorder()->GetEntriesByName(Entry::kEntryName);
+
+    ASSERT_FALSE(entries.empty());
+
+    for (const auto* const entry : entries) {
+      if (ukm_recorder()->GetEntryMetric(entry, metric_name))
+        FAIL() << "Unexpected UKM was recorded";
+    }
+    SUCCEED();
+  }
+
  protected:
   mojo::Remote<blink::mojom::FederatedAuthRequest> request_remote_;
   // Note: `auth_request_service_` owns itself, and will generally be deleted
@@ -739,6 +807,9 @@
   AccountList displayed_accounts_;
 
   GURL provider_;
+
+ private:
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
 };
 
 class BasicFederatedAuthRequestImplTest
@@ -767,6 +838,18 @@
   EXPECT_EQ(auth_response.second, test_case.expected.token);
 }
 
+TEST_P(BasicFederatedAuthRequestImplTest, FederatedAuthRequestIssue) {
+  AuthRequestTestCase test_case = GetParam();
+  CreateAuthRequest(GURL(test_case.inputs.provider));
+  SetMockExpectations(test_case);
+  auto auth_response = PerformAuthRequest(
+      test_case.inputs.client_id, test_case.inputs.nonce, test_case.inputs.mode,
+      test_case.inputs.prefer_auto_sign_in);
+  EXPECT_EQ(
+      main_test_rfh()->GetFederatedAuthRequestIssueCount(auth_response.first),
+      auth_response.first == RequestIdTokenStatus::kSuccess ? 0 : 1);
+}
+
 // Test Logout method success with multiple relying parties.
 TEST_F(BasicFederatedAuthRequestImplTest, LogoutSuccessMultiple) {
   CreateAuthRequest(GURL(kIdpTestOrigin));
@@ -1124,11 +1207,21 @@
         EXPECT_EQ(kAccountId, account_id);
         std::move(callback).Run(RevokeResponse::kSuccess);
       }));
+
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
   auto status = PerformRevokeRequest(kAccountId);
   EXPECT_EQ(RevokeStatus::kSuccess, status);
+
+  ukm_loop.Run();
+
   histogram_tester.ExpectTotalCount("Blink.FedCm.Status.Revoke", 1);
   histogram_tester.ExpectBucketCount("Blink.FedCm.Status.Revoke",
                                      RevokeStatusForMetrics::kSuccess, 1);
+
+  ExpectRevokeStatusUKM(RevokeStatusForMetrics::kSuccess);
 }
 
 TEST_F(FederatedAuthRequestImplTest, RevokeNoPermission) {
@@ -1146,12 +1239,20 @@
       HasRequestPermission(_, url::Origin::Create(GURL(kIdpTestOrigin))))
       .WillOnce(Return(false));
 
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
   auto status = PerformRevokeRequest(kAccountId);
   EXPECT_EQ(RevokeStatus::kError, status);
+
+  ukm_loop.Run();
   histogram_tester.ExpectTotalCount("Blink.FedCm.Status.Revoke", 1);
   histogram_tester.ExpectBucketCount("Blink.FedCm.Status.Revoke",
                                      RevokeStatusForMetrics::kNoAccountToRevoke,
                                      1);
+
+  ExpectRevokeStatusUKM(RevokeStatusForMetrics::kNoAccountToRevoke);
 }
 
 TEST_F(BasicFederatedAuthRequestImplTest, MetricsForSuccessfulSignUpCase) {
@@ -1166,11 +1267,18 @@
       &mock_sharing_permission_delegate);
 
   EXPECT_EQ(test_case.config.Mediated_conf.accounts.size(), 1u);
+
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
   auto auth_response = PerformAuthRequest(
       test_case.inputs.client_id, test_case.inputs.nonce, test_case.inputs.mode,
       test_case.inputs.prefer_auto_sign_in);
   EXPECT_EQ(auth_response.second.value(), kToken);
 
+  ukm_loop.Run();
+
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog", 1);
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
@@ -1180,6 +1288,14 @@
   histogram_tester.ExpectTotalCount("Blink.FedCm.Status.RequestIdToken", 1);
   histogram_tester.ExpectBucketCount("Blink.FedCm.Status.RequestIdToken",
                                      IdTokenStatus::kSuccess, 1);
+
+  ExpectTimingUKM("Timing.ShowAccountsDialog");
+  ExpectTimingUKM("Timing.ContinueOnDialog");
+  ExpectTimingUKM("Timing.IdTokenResponse");
+  ExpectTimingUKM("Timing.TurnaroundTime");
+  ExpectNoTimingUKM("Timing.CancelOnDialog");
+
+  ExpectRequestIdTokenStatusUKM(IdTokenStatus::kSuccess);
 }
 
 TEST_F(BasicFederatedAuthRequestImplTest, MetricsForSuccessfulSignInCase) {
@@ -1199,11 +1315,17 @@
                   url::Origin::Create(GURL(kIdpTestOrigin)), _, "1234"))
       .WillOnce(Return(true));
 
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
   auto auth_response = PerformAuthRequest(
       test_case.inputs.client_id, test_case.inputs.nonce, test_case.inputs.mode,
       test_case.inputs.prefer_auto_sign_in);
   EXPECT_EQ(LoginState::kSignIn, displayed_accounts()[0].login_state);
 
+  ukm_loop.Run();
+
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog", 1);
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
   histogram_tester.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
@@ -1213,6 +1335,14 @@
   histogram_tester.ExpectTotalCount("Blink.FedCm.Status.RequestIdToken", 1);
   histogram_tester.ExpectBucketCount("Blink.FedCm.Status.RequestIdToken",
                                      IdTokenStatus::kSuccess, 1);
+
+  ExpectTimingUKM("Timing.ShowAccountsDialog");
+  ExpectTimingUKM("Timing.ContinueOnDialog");
+  ExpectTimingUKM("Timing.IdTokenResponse");
+  ExpectTimingUKM("Timing.TurnaroundTime");
+  ExpectNoTimingUKM("Timing.CancelOnDialog");
+
+  ExpectRequestIdTokenStatusUKM(IdTokenStatus::kSuccess);
 }
 
 TEST_F(BasicFederatedAuthRequestImplTest, MetricsForNotSelectingAccount) {
@@ -1259,10 +1389,17 @@
           }));
 
   EXPECT_EQ(test_case.config.Mediated_conf.accounts.size(), 1u);
+
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
   auto auth_response = PerformAuthRequest(
       test_case.inputs.client_id, test_case.inputs.nonce, test_case.inputs.mode,
       test_case.inputs.prefer_auto_sign_in);
 
+  ukm_loop.Run();
+
   ASSERT_FALSE(displayed_accounts.empty());
   EXPECT_EQ(displayed_accounts[0].login_state, LoginState::kSignUp);
 
@@ -1275,6 +1412,14 @@
   histogram_tester.ExpectTotalCount("Blink.FedCm.Status.RequestIdToken", 1);
   histogram_tester.ExpectBucketCount("Blink.FedCm.Status.RequestIdToken",
                                      IdTokenStatus::kNotSelectAccount, 1);
+
+  ExpectTimingUKM("Timing.ShowAccountsDialog");
+  ExpectTimingUKM("Timing.CancelOnDialog");
+  ExpectNoTimingUKM("Timing.ContinueOnDialog");
+  ExpectNoTimingUKM("Timing.IdTokenResponse");
+  ExpectNoTimingUKM("Timing.TurnaroundTime");
+
+  ExpectRequestIdTokenStatusUKM(IdTokenStatus::kNotSelectAccount);
 }
 
 }  // namespace content
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 37efb34..354da6e2d 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -114,6 +114,7 @@
 
 std::unique_ptr<network::ResourceRequest> CreateCredentialedResourceRequest(
     GURL target_url,
+    bool send_referrer,
     url::Origin initiator) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   auto target_origin = url::Origin::Create(target_url);
@@ -122,6 +123,11 @@
   resource_request->request_initiator = initiator;
   resource_request->url = target_url;
   resource_request->site_for_cookies = site_for_cookies;
+  if (send_referrer) {
+    resource_request->referrer = initiator.GetURL();
+    resource_request->referrer_policy =
+        net::ReferrerPolicy::ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
+  }
   resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
                                       kRequestBodyContentType);
 
@@ -329,7 +335,8 @@
   GURL target_url =
       provider_.Resolve(IdpNetworkRequestManager::kWellKnownFilePath);
 
-  url_loader_ = CreateUncredentialedUrlLoader(target_url);
+  url_loader_ =
+      CreateUncredentialedUrlLoader(target_url, /* send_referrer= */ false);
 
   url_loader_->DownloadToString(
       loader_factory_.get(),
@@ -350,7 +357,8 @@
   std::string escaped_request = net::EscapeUrlEncodedData(request, true);
 
   GURL target_url = GURL(signin_url.spec() + "?" + escaped_request);
-  url_loader_ = CreateCredentialedUrlLoader(target_url);
+  url_loader_ =
+      CreateCredentialedUrlLoader(target_url, /* send_referrer= */ true);
   url_loader_->DownloadToString(
       loader_factory_.get(),
       base::BindOnce(&IdpNetworkRequestManager::OnSigninRequestResponse,
@@ -366,13 +374,8 @@
     AccountsRequestCallback callback) {
   DCHECK(!url_loader_);
 
-  // Use ReferrerPolicy::NO_REFERRER for this request so that relying party
-  // identity is not exposed to the Identity provider via referrer.
-  // TODO(cbiesinger): I don't think this does the right thing; per comments
-  // in referrer_policy.h this only applies to redirects.
-  net::ReferrerPolicy policy = net::ReferrerPolicy::NO_REFERRER;
   url_loader_ =
-      CreateCredentialedUrlLoader(accounts_url, absl::nullopt, policy);
+      CreateCredentialedUrlLoader(accounts_url, /* send_referrer= */ false);
 
   url_loader_->DownloadToString(
       loader_factory_.get(),
@@ -422,7 +425,8 @@
     return;
   }
 
-  url_loader_ = CreateCredentialedUrlLoader(token_url, request);
+  url_loader_ = CreateCredentialedUrlLoader(token_url,
+                                            /* send_referrer= */ true, request);
 
   url_loader_->DownloadToString(
       loader_factory_.get(),
@@ -480,7 +484,8 @@
     return;
   }
 
-  url_loader_ = CreateCredentialedUrlLoader(revoke_url, revoke_request_body);
+  url_loader_ = CreateCredentialedUrlLoader(
+      revoke_url, /* send_referrer= */ true, revoke_request_body);
 
   url_loader_->DownloadToString(
       loader_factory_.get(),
@@ -498,8 +503,8 @@
 
   logout_callback_ = std::move(callback);
 
-  auto resource_request =
-      CreateCredentialedResourceRequest(logout_url, relying_party_origin_);
+  auto resource_request = CreateCredentialedResourceRequest(
+      logout_url, /* send_referrer= */ false, relying_party_origin_);
   resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept, "*/*");
 
   auto traffic_annotation = CreateTrafficAnnotation();
@@ -769,7 +774,8 @@
   GURL target_url = endpoint.Resolve(
       "?client_id=" + net::EscapeQueryParamValue(client_id, true));
 
-  url_loader_ = CreateUncredentialedUrlLoader(target_url);
+  url_loader_ =
+      CreateUncredentialedUrlLoader(target_url, /* send_referrer= */ true);
 
   url_loader_->DownloadToString(
       loader_factory_.get(),
@@ -822,7 +828,8 @@
 
 std::unique_ptr<network::SimpleURLLoader>
 IdpNetworkRequestManager::CreateUncredentialedUrlLoader(
-    const GURL& target_url) const {
+    const GURL& target_url,
+    bool send_referrer) const {
   net::NetworkTrafficAnnotationTag traffic_annotation =
       CreateTrafficAnnotation();
 
@@ -840,6 +847,11 @@
   resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
                                       kRequestBodyContentType);
   AddCsrfHeader(resource_request.get());
+  if (send_referrer) {
+    resource_request->referrer = relying_party_origin_.GetURL();
+    // Since referrer_policy only affects redirects and we disable redirects
+    // below, we don't need to set referrer_policy here.
+  }
   // TODO(kenrb): Not following redirects is important for security because
   // this bypasses CORB. Ensure there is a test added.
   // https://crbug.com/1155312.
@@ -857,12 +869,10 @@
 std::unique_ptr<network::SimpleURLLoader>
 IdpNetworkRequestManager::CreateCredentialedUrlLoader(
     const GURL& target_url,
-    absl::optional<std::string> request_body,
-    absl::optional<net::ReferrerPolicy> policy) const {
-  auto resource_request =
-      CreateCredentialedResourceRequest(target_url, relying_party_origin_);
-  if (policy)
-    resource_request->referrer_policy = *policy;
+    bool send_referrer,
+    absl::optional<std::string> request_body) const {
+  auto resource_request = CreateCredentialedResourceRequest(
+      target_url, send_referrer, relying_party_origin_);
   if (request_body) {
     resource_request->method = net::HttpRequestHeaders::kPostMethod;
     resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h
index 82c6e3d..0fe1850 100644
--- a/content/browser/webid/idp_network_request_manager.h
+++ b/content/browser/webid/idp_network_request_manager.h
@@ -211,11 +211,12 @@
   void OnLogoutCompleted(std::unique_ptr<std::string> response_body);
 
   std::unique_ptr<network::SimpleURLLoader> CreateUncredentialedUrlLoader(
-      const GURL& url) const;
+      const GURL& url,
+      bool send_referrer) const;
   std::unique_ptr<network::SimpleURLLoader> CreateCredentialedUrlLoader(
       const GURL& url,
-      absl::optional<std::string> request_body = absl::nullopt,
-      absl::optional<net::ReferrerPolicy> policy = absl::nullopt) const;
+      bool send_referrer,
+      absl::optional<std::string> request_body = absl::nullopt) const;
 
   // URL of the Identity Provider.
   GURL provider_;
diff --git a/content/browser/webid/idp_network_request_manager_unittest.cc b/content/browser/webid/idp_network_request_manager_unittest.cc
index 75c77bf..5f1cc74 100644
--- a/content/browser/webid/idp_network_request_manager_unittest.cc
+++ b/content/browser/webid/idp_network_request_manager_unittest.cc
@@ -23,6 +23,7 @@
 #include "url/gurl.h"
 
 using AccountList = content::IdpNetworkRequestManager::AccountList;
+using ClientIdMetadata = content::IdpNetworkRequestManager::ClientIdMetadata;
 using FetchStatus = content::IdpNetworkRequestManager::FetchStatus;
 using AccountsRequestCallback =
     content::IdpNetworkRequestManager::AccountsRequestCallback;
@@ -39,6 +40,9 @@
 const char kTestIdpUrl[] = "https://idp.test";
 const char kTestRpUrl[] = "https://rp.test";
 const char kTestAccountsEndpoint[] = "https://idp.test/accounts_endpoint";
+const char kTestTokenEndpoint[] = "https://idp.test/token_endpoint";
+const char kTestClientIdMetadataEndpoint[] =
+    "https://idp.test/client_id_metadata_endpoint";
 const char kTestRevokeEndpoint[] = "https://idp.test/revoke_endpoint";
 
 class IdpNetworkRequestManagerTest : public ::testing::Test {
@@ -86,6 +90,50 @@
             std::move(parsed_idp_metadata)};
   }
 
+  std::string SendTokenRequestAndWaitForResponse(
+      const char* account,
+      const char* request,
+      net::HttpStatusCode http_status = net::HTTP_OK) {
+    const char response[] = R"({"id_token": "token"})";
+    GURL token_endpoint(kTestTokenEndpoint);
+    test_url_loader_factory().AddResponse(token_endpoint.spec(), response,
+                                          http_status);
+
+    std::string token;
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting(
+        [&](FetchStatus status, const std::string& token_response) {
+          token = token_response;
+          run_loop.Quit();
+        });
+    manager().SendTokenRequest(token_endpoint, account, request,
+                               std::move(callback));
+    run_loop.Run();
+    return token;
+  }
+
+  ClientIdMetadata SendClientIdMetadataRequestAndWaitForResponse(
+      const char* client_id,
+      net::HttpStatusCode http_status = net::HTTP_OK) {
+    const char response[] = R"({})";
+    GURL client_id_endpoint(kTestClientIdMetadataEndpoint);
+    test_url_loader_factory().AddResponse(
+        client_id_endpoint.spec() + "?client_id=" + client_id, response,
+        http_status);
+
+    ClientIdMetadata data;
+    base::RunLoop run_loop;
+    auto callback = base::BindLambdaForTesting(
+        [&](FetchStatus status, ClientIdMetadata metadata) {
+          data = metadata;
+          run_loop.Quit();
+        });
+    manager().FetchClientIdMetadata(client_id_endpoint, client_id,
+                                    std::move(callback));
+    run_loop.Run();
+    return data;
+  }
+
   RevokeResponse SendRevokeRequestAndWaitForResponse(
       const char* client_id,
       const char* account_id,
@@ -585,12 +633,90 @@
   EXPECT_EQ(FetchStatus::kSuccess, accounts_response);
 }
 
+// Tests that we send the correct referrer for account requests.
+TEST_F(IdpNetworkRequestManagerTest, AccountRequestReferrer) {
+  bool called = false;
+  auto interceptor =
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        called = true;
+        EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url);
+        EXPECT_EQ(request.request_body, nullptr);
+        EXPECT_EQ(false, request.referrer.is_valid());
+      });
+  test_url_loader_factory().SetInterceptor(interceptor);
+
+  const char test_accounts_json[] = R"({
+  "accounts" : [
+    {
+      "sub" : "1234",
+      "email": "ken@idp.test",
+      "name": "Ken R. Example"
+    }
+  ]
+  })";
+
+  FetchStatus accounts_response;
+  AccountList accounts;
+  IdentityProviderMetadata idp_metadata;
+  std::tie(accounts_response, accounts, idp_metadata) =
+      SendAccountsRequestAndWaitForResponse(test_accounts_json);
+
+  ASSERT_TRUE(called);
+  EXPECT_EQ(FetchStatus::kSuccess, accounts_response);
+}
+
+// Tests the token request implementation.
+TEST_F(IdpNetworkRequestManagerTest, TokenRequest) {
+  bool called = false;
+  auto interceptor =
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        called = true;
+        EXPECT_EQ(GURL(kTestTokenEndpoint), request.url);
+        EXPECT_EQ(GURL(kTestRpUrl), request.referrer);
+
+        // Check that the request body is correct (should be "request")
+        ASSERT_NE(request.request_body, nullptr);
+        ASSERT_EQ(1ul, request.request_body->elements()->size());
+        const network::DataElement& elem =
+            request.request_body->elements()->at(0);
+        ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
+        const network::DataElementBytes& byte_elem =
+            elem.As<network::DataElementBytes>();
+        EXPECT_EQ("request", byte_elem.AsStringPiece());
+      });
+  test_url_loader_factory().SetInterceptor(interceptor);
+  std::string token = SendTokenRequestAndWaitForResponse("account", "request");
+  ASSERT_TRUE(called);
+  ASSERT_EQ("token", token);
+}
+
+// Tests the client id metadata implementation.
+TEST_F(IdpNetworkRequestManagerTest, ClientIdMetadata) {
+  bool called = false;
+  auto interceptor =
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        called = true;
+        std::string url_string =
+            std::string(kTestClientIdMetadataEndpoint) + "?client_id=xxx";
+        EXPECT_EQ(GURL(url_string), request.url);
+        EXPECT_EQ(request.request_body, nullptr);
+        EXPECT_EQ(GURL(kTestRpUrl), request.referrer);
+      });
+  test_url_loader_factory().SetInterceptor(interceptor);
+  ClientIdMetadata data = SendClientIdMetadataRequestAndWaitForResponse("xxx");
+  ASSERT_TRUE(called);
+  ASSERT_EQ("", data.privacy_policy_url);
+  ASSERT_EQ("", data.terms_of_service_url);
+}
+
 // Tests the revoke implementation.
 TEST_F(IdpNetworkRequestManagerTest, Revoke) {
   bool called = false;
   auto interceptor =
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
         called = true;
+        EXPECT_EQ(GURL(kTestRpUrl), request.referrer);
+        // Check that the request body is correct
         ASSERT_NE(request.request_body, nullptr);
         ASSERT_EQ(1ul, request.request_body->elements()->size());
         const network::DataElement& elem =
diff --git a/content/browser/webui/content_web_ui_controller_factory.cc b/content/browser/webui/content_web_ui_controller_factory.cc
index 3d7511e..1324e1c9 100644
--- a/content/browser/webui/content_web_ui_controller_factory.cc
+++ b/content/browser/webui/content_web_ui_controller_factory.cc
@@ -46,7 +46,7 @@
       url.host_piece() == kChromeUIPrerenderInternalsHost ||
       url.host_piece() == kChromeUIProcessInternalsHost ||
       url.host_piece() == kChromeUIAttributionInternalsHost ||
-      url.host_piece() == kChromeUIQuotaInternals2Host ||
+      url.host_piece() == kChromeUIQuotaInternalsHost ||
       url.host_piece() == kChromeUIUkmHost) {
     return const_cast<ContentWebUIControllerFactory*>(this);
   }
@@ -86,8 +86,8 @@
     return std::make_unique<ProcessInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIAttributionInternalsHost)
     return std::make_unique<AttributionInternalsUI>(web_ui);
-  if (url.host_piece() == kChromeUIQuotaInternals2Host)
-    return std::make_unique<QuotaInternals2UI>(web_ui);
+  if (url.host_piece() == kChromeUIQuotaInternalsHost)
+    return std::make_unique<QuotaInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIUkmHost)
     return std::make_unique<UkmInternalsUI>(web_ui);
   if (url.host_piece() == kChromeUIMediaInternalsHost) {
diff --git a/content/public/common/url_constants.cc b/content/public/common/url_constants.cc
index 6122c0be..2e72ca2 100644
--- a/content/public/common/url_constants.cc
+++ b/content/public/common/url_constants.cc
@@ -38,7 +38,7 @@
 const char kChromeUINetworkErrorsListingHost[] = "network-errors";
 const char kChromeUIPrerenderInternalsHost[] = "prerender-internals";
 const char kChromeUIProcessInternalsHost[] = "process-internals";
-const char kChromeUIQuotaInternals2Host[] = "quota-internals-2";
+const char kChromeUIQuotaInternalsHost[] = "quota-internals";
 const char kChromeUIResourcesHost[] = "resources";
 const char kChromeUIServiceWorkerInternalsHost[] = "serviceworker-internals";
 const char kChromeUITracingHost[] = "tracing";
diff --git a/content/public/common/url_constants.h b/content/public/common/url_constants.h
index 587304b..ebbb5796 100644
--- a/content/public/common/url_constants.h
+++ b/content/public/common/url_constants.h
@@ -48,7 +48,7 @@
 CONTENT_EXPORT extern const char kChromeUINetworkErrorsListingHost[];
 CONTENT_EXPORT extern const char kChromeUIPrerenderInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIProcessInternalsHost[];
-CONTENT_EXPORT extern const char kChromeUIQuotaInternals2Host[];
+CONTENT_EXPORT extern const char kChromeUIQuotaInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIResourcesHost[];
 CONTENT_EXPORT extern const char kChromeUIServiceWorkerInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUITracingHost[];
diff --git a/content/test/data/attribution_reporting/databases/version_1.sql b/content/test/data/attribution_reporting/databases/version_1.sql
deleted file mode 100644
index 9188982..0000000
--- a/content/test/data/attribution_reporting/databases/version_1.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-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);
-
-INSERT INTO impressions
-VALUES(1,
-       '9357e17751666f64',
-       'https://impression.test',
-       'https://sub.conversion.test',
-       'https://report.test',
-       13245278349693988,
-       13247870349693988,
-       0,
-       1);
-
-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,attribution_credit INTEGER NOT NULL);
-
-CREATE INDEX conversion_origin_idx ON impressions(active,conversion_origin,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);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_10.sql b/content/test/data/attribution_reporting/databases/version_10.sql
deleted file mode 100644
index be8e732..0000000
--- a/content/test/data/attribution_reporting/databases/version_10.sql
+++ /dev/null
@@ -1,37 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority 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 dedup_keys(impression_id INTEGER NOT NULL,dedup_key INTEGER NOT NULL,PRIMARY KEY(impression_id,dedup_key))WITHOUT ROWID;
-
-CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
-
-INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','10');
-INSERT INTO meta VALUES('last_compatible_version','10');
-
-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 impression_site_idx ON impressions(active,impression_site,source_type);
-
-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/data/attribution_reporting/databases/version_11.sql b/content/test/data/attribution_reporting/databases/version_11.sql
deleted file mode 100644
index 617cdbe..0000000
--- a/content/test/data/attribution_reporting/databases/version_11.sql
+++ /dev/null
@@ -1,40 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority 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 dedup_keys(impression_id INTEGER NOT NULL,dedup_key INTEGER NOT NULL,PRIMARY KEY(impression_id,dedup_key))WITHOUT ROWID;
-
-CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
-
-INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','11');
-INSERT INTO meta VALUES('last_compatible_version','11');
-
-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 event_source_impression_site_idx ON impressions(impression_site)WHERE active = 1 AND num_conversions = 0 AND source_type = 1;
-
-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);
-
-INSERT INTO rate_limits VALUES
-  (1, 0, 11, 'https://a.example', 'https://a.a.example', 'https://b.example', 'https://b.b.example', 7);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_12.sql b/content/test/data/attribution_reporting/databases/version_12.sql
deleted file mode 100644
index 6db38be..0000000
--- a/content/test/data/attribution_reporting/databases/version_12.sql
+++ /dev/null
@@ -1,40 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority INTEGER NOT NULL);
-
-CREATE TABLE rate_limits(rate_limit_id INTEGER PRIMARY KEY NOT NULL,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,bucket TEXT NOT NULL,value INTEGER NOT NULL);
-
-CREATE TABLE dedup_keys(impression_id INTEGER NOT NULL,dedup_key INTEGER NOT NULL,PRIMARY KEY(impression_id,dedup_key))WITHOUT ROWID;
-
-CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
-
-INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','12');
-INSERT INTO meta VALUES('last_compatible_version','12');
-
-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 event_source_impression_site_idx ON impressions(impression_site)WHERE active = 1 AND num_conversions = 0 AND source_type = 1;
-
-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_attribution_type_conversion_time_idx ON rate_limits(attribution_type,conversion_time);
-
-CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
-
-INSERT INTO impressions VALUES (1,2,'a','b','c',3,4,5,6,'d',7,8,9,'e');
-INSERT INTO conversions VALUES (10,11,12,13,14,15);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_13.sql b/content/test/data/attribution_reporting/databases/version_13.sql
deleted file mode 100644
index 3b8fb02..0000000
--- a/content/test/data/attribution_reporting/databases/version_13.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority INTEGER NOT NULL);
-
-CREATE TABLE rate_limits(rate_limit_id INTEGER PRIMARY KEY NOT NULL,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,bucket TEXT NOT NULL,value INTEGER NOT NULL);
-
-CREATE TABLE dedup_keys(impression_id INTEGER NOT NULL,dedup_key INTEGER NOT NULL,PRIMARY KEY(impression_id,dedup_key))WITHOUT ROWID;
-
-CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
-
-INSERT INTO meta VALUES('mmap_status','-1');
-INSERT INTO meta VALUES('version','13');
-INSERT INTO meta VALUES('last_compatible_version','13');
-
-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 event_source_impression_site_idx ON impressions(impression_site)WHERE active = 1 AND num_conversions = 0 AND source_type = 1;
-
-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_attribution_type_conversion_time_idx ON rate_limits(attribution_type,conversion_time);
-
-CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
-
-INSERT INTO conversions VALUES (1,2,3,4,5,6);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_2.sql b/content/test/data/attribution_reporting/databases/version_2.sql
deleted file mode 100644
index bf3037a..0000000
--- a/content/test/data/attribution_reporting/databases/version_2.sql
+++ /dev/null
@@ -1,37 +0,0 @@
-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);
-
-INSERT INTO impressions
-VALUES(1,
-       '9357e17751666f64',
-       'https://impression.test',
-       'https://sub.conversion.test',
-       'https://report.test',
-       13245278349693988,
-       13247870349693988,
-       0,
-       1,
-       'https://conversion.test/');
-
-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,attribution_credit 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('last_compatible_version', '2');
-INSERT INTO meta VALUES('version', '2');
-
-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 conversion_destination_idx ON impressions(active,conversion_destination,reporting_origin);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_3.sql b/content/test/data/attribution_reporting/databases/version_3.sql
deleted file mode 100644
index 24a7f9f..0000000
--- a/content/test/data/attribution_reporting/databases/version_3.sql
+++ /dev/null
@@ -1,25 +0,0 @@
-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,attribution_credit 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','3');
-INSERT INTO meta VALUES('last_compatible_version','3');
-
-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);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_4.sql b/content/test/data/attribution_reporting/databases/version_4.sql
deleted file mode 100644
index 87c43673..0000000
--- a/content/test/data/attribution_reporting/databases/version_4.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-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,attribution_credit 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','4');
-INSERT INTO meta VALUES('last_compatible_version','3');
-
-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);
-
--- Add some conversions to test 0-credit deletion in version 5.
-INSERT INTO conversions (conversion_id, conversion_data, conversion_time, report_time, attribution_credit) VALUES
-  (1, 'a', 2, 3, 5),
-  (2, 'b', 7, 11, 0),
-  (3, 'c', 13, 17, 19);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_5.sql b/content/test/data/attribution_reporting/databases/version_5.sql
deleted file mode 100644
index 14f755b..0000000
--- a/content/test/data/attribution_reporting/databases/version_5.sql
+++ /dev/null
@@ -1,47 +0,0 @@
-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);
-
-INSERT INTO impressions
-VALUES(1,
-       '9357e17751666f64',
-       'https://impression.test',
-       'https://conversion.test',
-       'https://report.test',
-       13245278349693988,
-       13247870349693988,
-       0,
-       1,
-       'https://conversion.test/',
-       0,
-       1);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_6.sql b/content/test/data/attribution_reporting/databases/version_6.sql
deleted file mode 100644
index 67365cc..0000000
--- a/content/test/data/attribution_reporting/databases/version_6.sql
+++ /dev/null
@@ -1,61 +0,0 @@
-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,priority 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','6');
-INSERT INTO meta VALUES('last_compatible_version','6');
-
-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);
-
-INSERT INTO impressions
-VALUES (1,
-        '9357e17751666f64',
-        'https://a.impression.test',
-        'https://conversion.test',
-        'https://report.test',
-        13245278349693988,
-        13247870349693988,
-        0,
-        1,
-        'https://conversion.test/',
-        0,
-        1,
-        3),
-       (2,
-        '9357e17751666f64',
-        'https://b.impression.test',
-        'https://conversion.test',
-        'https://report.test',
-        13245278349693988,
-        13247870349693988,
-        0,
-        1,
-        'https://conversion.test/',
-        0,
-        1,
-        4);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_7.sql b/content/test/data/attribution_reporting/databases/version_7.sql
deleted file mode 100644
index 543d96f7..0000000
--- a/content/test/data/attribution_reporting/databases/version_7.sql
+++ /dev/null
@@ -1,69 +0,0 @@
-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,priority INTEGER NOT NULL,impression_site TEXT 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','7');
-INSERT INTO meta VALUES('last_compatible_version','7');
-
-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 impression_site_idx ON impressions(active,impression_site,source_type);
-
-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);
-
-INSERT INTO impressions
-VALUES (1,
-       '18446744073709551615',
-       'https://impression.test',
-       'https://conversion.test',
-       'https://report.test',
-       13245278349693988,
-       13247870349693988,
-       0,
-       1,
-       'https://conversion.test/',
-       0,
-       1,
-       3,
-       'https://impression.test'),
-       (2,
-       'invalid',
-       'https://impression2.test',
-       'https://conversion2.test',
-       'https://report2.test',
-       13245278349693988,
-       13247870349693988,
-       0,
-       1,
-       'https://conversion2.test/',
-       0,
-       1,
-       5,
-       'https://impression2.test');
-
-INSERT INTO conversions
-VALUES (0, 29, '234', 5, 6),
-       (7, NULL, 'invalid', 8, 9);
-
-COMMIT;
diff --git a/content/test/data/attribution_reporting/databases/version_8.sql b/content/test/data/attribution_reporting/databases/version_8.sql
deleted file mode 100644
index a9ec23e..0000000
--- a/content/test/data/attribution_reporting/databases/version_8.sql
+++ /dev/null
@@ -1,35 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY,impression_id INTEGER NOT NULL,conversion_data INTEGER 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','8');
-INSERT INTO meta VALUES('last_compatible_version','8');
-
-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 impression_site_idx ON impressions(active,impression_site,source_type);
-
-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/data/attribution_reporting/databases/version_9.sql b/content/test/data/attribution_reporting/databases/version_9.sql
deleted file mode 100644
index c60db91..0000000
--- a/content/test/data/attribution_reporting/databases/version_9.sql
+++ /dev/null
@@ -1,35 +0,0 @@
-PRAGMA foreign_keys=OFF;
-
-BEGIN TRANSACTION;
-
-CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data INTEGER 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,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
-
-CREATE TABLE conversions(conversion_id INTEGER PRIMARY KEY,impression_id INTEGER NOT NULL,conversion_data INTEGER NOT NULL,conversion_time INTEGER NOT NULL,report_time INTEGER NOT NULL,priority 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','9');
-INSERT INTO meta VALUES('last_compatible_version','9');
-
-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 impression_site_idx ON impressions(active,impression_site,source_type);
-
-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/gpu/gpu_tests/gpu_helper.py b/content/test/gpu/gpu_tests/gpu_helper.py
index 0b1924a..d29cc17 100644
--- a/content/test/gpu/gpu_tests/gpu_helper.py
+++ b/content/test/gpu/gpu_tests/gpu_helper.py
@@ -9,16 +9,11 @@
 
 # This set must be the union of the driver tags used in WebGL and WebGL2
 # expectations files.
+# Examples:
+#   intel_lt_25.20.100.6577
+#   mesa_ge_20.1
 EXPECTATIONS_DRIVER_TAGS = frozenset([
-    'intel_lt_25.20.100.6444',
-    'intel_lt_25.20.100.6577',
-    'intel_lt_26.20.100.7000',
-    'intel_lt_26.20.100.7323',
-    'intel_lt_26.20.100.7870',
-    'intel_lt_26.20.100.8141',
-    'intel_lt_27.20.100.8280',
     'mesa_lt_19.1',
-    'mesa_ge_20.1',
 ])
 
 # Driver tag format: VENDOR_OPERATION_VERSION
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index 26289d2..4e8d3b6 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index f2e5dd8..b08f9dff 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
index 6f645563..2b29477 100644
--- a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
index 3dd3202a..82b1b42 100644
--- a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
index 6f645563..2b29477 100644
--- a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
index c8915b3..ea184ad 100644
--- a/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/mediapipe_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 6f9a0c9..b7ffbf523 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
index 7337524..3868cfe6 100644
--- a/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/power_measurement_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
index 40db29d..e69d1fe 100644
--- a/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/screenshot_sync_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
index da5d0c73..e32c02a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
index b84cf5b..cfadaccf 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index eba5481f..91d79d7 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 140822c..69faf34 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -46,11 +46,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/gpu/validate_tag_consistency.py b/content/test/gpu/validate_tag_consistency.py
index cdd7625..6b84739 100755
--- a/content/test/gpu/validate_tag_consistency.py
+++ b/content/test/gpu/validate_tag_consistency.py
@@ -59,11 +59,7 @@
 # SwiftShader
 # tags: [ swiftshader-gl no-swiftshader-gl ]
 # Driver
-# tags: [ intel_lt_25.20.100.6444 intel_lt_25.20.100.6577
-#             intel_lt_26.20.100.7000 intel_lt_26.20.100.7870
-#             intel_lt_26.20.100.7323 intel_lt_26.20.100.8141
-#             intel_lt_27.20.100.8280
-#         mesa_lt_19.1 mesa_ge_20.1 ]
+# tags: [ mesa_lt_19.1 ]
 # ASan
 # tags: [ asan no-asan ]
 # Display Server
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index e49b1d5..36de614 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -136,6 +136,10 @@
         heavy_ad_issue_cpu_peak_count_++;
         break;
     }
+  } else if (issue->code ==
+             blink::mojom::InspectorIssueCode::kFederatedAuthRequestIssue) {
+    ++federated_auth_counts_[issue->details->federated_auth_request_details
+                                 ->status];
   }
   RenderFrameHostImpl::ReportInspectorIssue(std::move(issue));
 }
@@ -236,6 +240,14 @@
   }
 }
 
+int TestRenderFrameHost::GetFederatedAuthRequestIssueCount(
+    blink::mojom::RequestIdTokenStatus status) {
+  auto it = federated_auth_counts_.find(status);
+  if (it == federated_auth_counts_.end())
+    return 0;
+  return it->second;
+}
+
 void TestRenderFrameHost::SimulateManifestURLUpdate(const GURL& manifest_url) {
   // TODO(crbug.com/1222510): Add TestPage and use it.
   GetPage().UpdateManifestUrl(manifest_url);
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h
index d86768d3b..c6592a8 100644
--- a/content/test/test_render_frame_host.h
+++ b/content/test/test_render_frame_host.h
@@ -139,6 +139,11 @@
   void DidEnforceInsecureRequestPolicy(
       blink::mojom::InsecureRequestPolicy policy);
 
+  // Returns the number of FedCM issues sent to DevTools with the given
+  // RequestIdTokenStatus.
+  int GetFederatedAuthRequestIssueCount(
+      blink::mojom::RequestIdTokenStatus status);
+
   // If set, navigations will appear to have cleared the history list in the
   // RenderFrame (DidCommitProvisionalLoadParams::history_list_was_cleared).
   // False by default.
@@ -292,6 +297,11 @@
   int heavy_ad_issue_cpu_total_count_ = 0;
   int heavy_ad_issue_cpu_peak_count_ = 0;
 
+  // Keeps a count of federated authentication request issues sent to
+  // ReportInspectorIssue.
+  std::unordered_map<blink::mojom::RequestIdTokenStatus, int>
+      federated_auth_counts_;
+
   TestRenderFrameHostCreationObserver child_creation_observer_;
 
   // See set_simulate_history_list_was_cleared() above.
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn
index c2112ab..b1731400 100644
--- a/extensions/BUILD.gn
+++ b/extensions/BUILD.gn
@@ -386,40 +386,4 @@
       deps += [ "//components/user_manager:test_support" ]
     }
   }
-
-  # TODO(rockot) bug 505926: This should be deleted for the same reason as
-  # chrome_extensions_browsertests.
-  source_set("chrome_extensions_interactive_uitests") {
-    testonly = true
-    sources = [ "browser/guest_view/mime_handler_view/mime_handler_view_interactive_uitest.cc" ]
-
-    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-
-    # These are the deps from interactive_uitests minus some internal Chrome
-    # ones that aren't allowed to be included here and that aren't needed.
-    deps = [
-      "//chrome/browser",
-      "//chrome/browser/devtools",
-      "//chrome/renderer",
-      "//chrome/test:test_support",
-      "//components/sync",
-      "//content/app/resources",
-      "//crypto:platform",
-      "//crypto:test_support",
-      "//google_apis:test_support",
-      "//net",
-      "//net:net_resources",
-      "//net:test_support",
-      "//skia",
-      "//testing/gmock",
-      "//testing/gtest",
-      "//third_party/hunspell",
-      "//third_party/icu",
-      "//third_party/libpng",
-      "//third_party/zlib",
-      "//ui/base:test_support",
-      "//ui/resources:ui_test_pak",
-      "//ui/web_dialogs:test_support",
-    ]
-  }
 }
diff --git a/extensions/DEPS b/extensions/DEPS
index dbcdddc2..b4e90cc0 100644
--- a/extensions/DEPS
+++ b/extensions/DEPS
@@ -41,8 +41,4 @@
   ".*(test|test_util)\.(cc|h)$": [
     "+content/public/test",
   ],
-  "mime_handler_view_interactive_uitest.cc": [
-    "+chrome/browser/ui/exclusive_access/exclusive_access_test.h",
-    "+chrome/test/base/interactive_test_utils.h",
-  ],
 }
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index a0b24d59..ede7cb8c 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -435,6 +435,8 @@
     "install_stage.h",
     "json_file_sanitizer.cc",
     "json_file_sanitizer.h",
+    "l10n_file_util.cc",
+    "l10n_file_util.h",
     "lazy_background_task_queue.cc",
     "lazy_background_task_queue.h",
     "lazy_background_task_queue_factory.cc",
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index a492eba..1153141 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -151,7 +151,7 @@
 
 // static
 void EventRouter::DispatchExtensionMessage(
-    IPC::Sender* ipc_sender,
+    content::RenderProcessHost* rph,
     int worker_thread_id,
     content::BrowserContext* browser_context,
     const std::string& extension_id,
@@ -169,7 +169,29 @@
   params.is_user_gesture = user_gesture == USER_GESTURE_ENABLED;
   params.filtering_info = info->To<EventFilteringInfo>();
 
-  ipc_sender->Send(new ExtensionMsg_DispatchEvent(params, *event_args));
+  // TODO(crbug/1222550): Remove IPC->Send call after worker_thread_dispatcher
+  // is also mojofied.
+  if (worker_thread_id == kMainThreadId) {
+    Get(browser_context)->RouteDispatchEvent(rph, params.Clone(), *event_args);
+  } else {
+    rph->Send(new ExtensionMsg_DispatchEvent(params, *event_args));
+  }
+}
+
+void EventRouter::RouteDispatchEvent(content::RenderProcessHost* rph,
+                                     const mojom::DispatchEventParamsPtr params,
+                                     const ListValue& event_args) {
+  mojo::AssociatedRemote<mojom::EventDispatcher>& dispatcher =
+      rph_dispatcher_map_[rph];
+  if (!dispatcher.is_bound()) {
+    IPC::ChannelProxy* channel = rph->GetChannel();
+    if (!channel) {
+      return;
+    }
+    channel->GetRemoteAssociatedInterface(
+        dispatcher.BindNewEndpointAndPassReceiver());
+  }
+  dispatcher->DispatchEvent(params.Clone(), event_args.Clone());
 }
 
 // static
@@ -185,7 +207,7 @@
 
 // static
 void EventRouter::DispatchEventToSender(
-    IPC::Sender* ipc_sender,
+    content::RenderProcessHost* rph,
     content::BrowserContext* browser_context,
     const std::string& extension_id,
     events::HistogramValue histogram_value,
@@ -202,8 +224,8 @@
       browser_context, extension_id, event_id, render_process_id,
       service_worker_version_id, histogram_value, event_name);
 
-  DispatchExtensionMessage(ipc_sender, worker_thread_id, browser_context,
-                           extension_id, event_id, event_name, event_args.get(),
+  DispatchExtensionMessage(rph, worker_thread_id, browser_context, extension_id,
+                           event_id, event_name, event_args.get(),
                            UserGestureState::USER_GESTURE_UNKNOWN,
                            std::move(info));
 }
@@ -570,6 +592,7 @@
     const content::ChildProcessTerminationInfo& info) {
   listeners_.RemoveListenersForProcess(host);
   observed_process_set_.erase(host);
+  rph_dispatcher_map_.erase(host);
   host->RemoveObserver(this);
 }
 
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index f310f7a0..e4b87de 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -30,10 +30,11 @@
 #include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/features/feature.h"
-#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
+#include "extensions/common/mojom/event_dispatcher.mojom.h"
 #include "extensions/common/mojom/event_router.mojom.h"
 #include "ipc/ipc_sender.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "url/gurl.h"
 
@@ -118,7 +119,7 @@
   // methods BroadcastEvent or DispatchEventToExtension.
   // Note that this method will dispatch the event with
   // UserGestureState:USER_GESTURE_UNKNOWN.
-  static void DispatchEventToSender(IPC::Sender* ipc_sender,
+  static void DispatchEventToSender(content::RenderProcessHost* rph,
                                     content::BrowserContext* browser_context,
                                     const std::string& extension_id,
                                     events::HistogramValue histogram_value,
@@ -356,7 +357,7 @@
 
   // TODO(gdk): Document this.
   static void DispatchExtensionMessage(
-      IPC::Sender* ipc_sender,
+      content::RenderProcessHost* rph,
       int worker_thread_id,
       content::BrowserContext* browser_context,
       const std::string& extension_id,
@@ -445,6 +446,10 @@
                                const std::string& event_name,
                                int64_t service_worker_version_id);
 
+  void RouteDispatchEvent(content::RenderProcessHost* rph,
+                          const mojom::DispatchEventParamsPtr params,
+                          const base::ListValue& event_args);
+
   // static
   static void DoDispatchEventToSenderBookkeeping(
       content::BrowserContext* context,
@@ -494,6 +499,10 @@
 
   EventAckData event_ack_data_;
 
+  std::map<content::RenderProcessHost*,
+           mojo::AssociatedRemote<mojom::EventDispatcher>>
+      rph_dispatcher_map_;
+
   // All the Mojo receivers for the EventRouter. Keeps track of the render
   // process id.
   mojo::AssociatedReceiverSet<mojom::EventRouter, int /*render_process_id*/>
diff --git a/extensions/browser/extension_user_script_loader.cc b/extensions/browser/extension_user_script_loader.cc
index 466e202..4ac6c6e 100644
--- a/extensions/browser/extension_user_script_loader.cc
+++ b/extensions/browser/extension_user_script_loader.cc
@@ -38,10 +38,10 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/l10n_file_util.h"
 #include "extensions/browser/state_store.h"
 #include "extensions/browser/user_script_loader.h"
 #include "extensions/common/api/content_scripts.h"
-#include "extensions/common/file_util.h"
 #include "extensions/common/manifest_handlers/content_scripts_handler.h"
 #include "extensions/common/manifest_handlers/default_locale_handler.h"
 #include "extensions/common/message_bundle.h"
@@ -199,7 +199,7 @@
     }
     if (script->css_scripts().size() > 0) {
       std::unique_ptr<SubstitutionMap> localization_messages(
-          file_util::LoadMessageBundleSubstitutionMap(
+          l10n_file_util::LoadMessageBundleSubstitutionMap(
               host_info.file_path, script->host_id().id,
               host_info.default_locale, host_info.gzip_permission));
 
diff --git a/extensions/browser/l10n_file_util.cc b/extensions/browser/l10n_file_util.cc
new file mode 100644
index 0000000..3d37974
--- /dev/null
+++ b/extensions/browser/l10n_file_util.cc
@@ -0,0 +1,63 @@
+// Copyright 2022 The Chromium 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 "extensions/browser/l10n_file_util.h"
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "extensions/common/file_util.h"
+
+namespace extensions::l10n_file_util {
+
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
+    const base::FilePath& extension_path,
+    const ExtensionId& extension_id,
+    const std::string& default_locale,
+    extension_l10n_util::GzippedMessagesPermission gzip_permission) {
+  return LoadMessageBundleSubstitutionMapFromPaths(
+      {extension_path}, extension_id, default_locale, gzip_permission);
+}
+
+MessageBundle::SubstitutionMap* LoadNonLocalizedMessageBundleSubstitutionMap(
+    const ExtensionId& extension_id) {
+  MessageBundle::SubstitutionMap* return_value =
+      new MessageBundle::SubstitutionMap();
+
+  // Add @@extension_id reserved message here.
+  return_value->insert(
+      std::make_pair(MessageBundle::kExtensionIdKey, extension_id));
+
+  return return_value;
+}
+
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapFromPaths(
+    const std::vector<base::FilePath>& paths,
+    const ExtensionId& extension_id,
+    const std::string& default_locale,
+    extension_l10n_util::GzippedMessagesPermission gzip_permission) {
+  MessageBundle::SubstitutionMap* return_value =
+      LoadNonLocalizedMessageBundleSubstitutionMap(extension_id);
+
+  // Touch disk only if extension is localized.
+  if (default_locale.empty())
+    return return_value;
+
+  std::string error;
+  for (const base::FilePath& path : paths) {
+    std::unique_ptr<MessageBundle> bundle(file_util::LoadMessageBundle(
+        path, default_locale, gzip_permission, &error));
+    if (bundle) {
+      for (const auto& iter : *bundle->dictionary()) {
+        // |insert| only adds new entries, and does not replace entries in
+        // the main extension or previously processed imports.
+        return_value->insert(std::make_pair(iter.first, iter.second));
+      }
+    }
+  }
+
+  return return_value;
+}
+
+}  // namespace extensions::l10n_file_util
diff --git a/extensions/browser/l10n_file_util.h b/extensions/browser/l10n_file_util.h
new file mode 100644
index 0000000..4bb5e0d
--- /dev/null
+++ b/extensions/browser/l10n_file_util.h
@@ -0,0 +1,54 @@
+// Copyright 2022 The Chromium 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 EXTENSIONS_BROWSER_L10N_FILE_UTIL_H_
+#define EXTENSIONS_BROWSER_L10N_FILE_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "extensions/common/extension_id.h"
+#include "extensions/common/message_bundle.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace extension_l10n_util {
+enum class GzippedMessagesPermission;
+}
+
+namespace extensions::l10n_file_util {
+
+// TODO(devlin): All these methods return ownership of the object. Make them
+// return unique_ptrs.
+
+// Loads the extension message bundle substitution map. Contains at least
+// the extension_id item. Does not supported compressed locale files. Passes
+// |gzip_permission| to extension_l10n_util::LoadMessageCatalogs (see
+// extension_l10n_util.h).
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
+    const base::FilePath& extension_path,
+    const ExtensionId& extension_id,
+    const std::string& default_locale,
+    extension_l10n_util::GzippedMessagesPermission gzip_permission);
+
+// Loads the extension message bundle substitution map for a non-localized
+// extension. Contains only the extension_id item.
+// This doesn't require hitting disk, so it's safe to call on any thread.
+MessageBundle::SubstitutionMap* LoadNonLocalizedMessageBundleSubstitutionMap(
+    const ExtensionId& extension_id);
+
+// Loads the extension message bundle substitution map from the specified paths.
+// Contains at least the extension_id item. Passes |gzip_permission| to
+// extension_l10n_util::LoadMessageCatalogs (see extension_l10n_util.h).
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapFromPaths(
+    const std::vector<base::FilePath>& paths,
+    const ExtensionId& extension_id,
+    const std::string& default_locale,
+    extension_l10n_util::GzippedMessagesPermission gzip_permission);
+
+}  // namespace extensions::l10n_file_util
+
+#endif  // EXTENSIONS_BROWSER_L10N_FILE_UTIL_H_
diff --git a/extensions/browser/load_and_localize_file.cc b/extensions/browser/load_and_localize_file.cc
index 33b296c..2fe6546 100644
--- a/extensions/browser/load_and_localize_file.cc
+++ b/extensions/browser/load_and_localize_file.cc
@@ -11,11 +11,11 @@
 #include "extensions/browser/component_extension_resource_manager.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/file_reader.h"
+#include "extensions/browser/l10n_file_util.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/extension_l10n_util.h"
 #include "extensions/common/extension_resource.h"
-#include "extensions/common/file_util.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/message_bundle.h"
@@ -39,9 +39,9 @@
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
   std::unique_ptr<MessageBundle::SubstitutionMap> localization_messages(
-      file_util::LoadMessageBundleSubstitutionMap(extension_path, extension_id,
-                                                  extension_default_locale,
-                                                  gzip_permission));
+      l10n_file_util::LoadMessageBundleSubstitutionMap(
+          extension_path, extension_id, extension_default_locale,
+          gzip_permission));
 
   std::string error;
   MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages,
diff --git a/extensions/common/file_util.cc b/extensions/common/file_util.cc
index 35aa018..eab1b26be 100644
--- a/extensions/common/file_util.cc
+++ b/extensions/common/file_util.cc
@@ -546,55 +546,6 @@
   return message_bundle;
 }
 
-MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
-    const base::FilePath& extension_path,
-    const std::string& extension_id,
-    const std::string& default_locale,
-    extension_l10n_util::GzippedMessagesPermission gzip_permission) {
-  return LoadMessageBundleSubstitutionMapFromPaths(
-      {extension_path}, extension_id, default_locale, gzip_permission);
-}
-
-MessageBundle::SubstitutionMap* LoadNonLocalizedMessageBundleSubstitutionMap(
-    const std::string& extension_id) {
-  MessageBundle::SubstitutionMap* return_value =
-      new MessageBundle::SubstitutionMap();
-
-  // Add @@extension_id reserved message here.
-  return_value->insert(
-      std::make_pair(MessageBundle::kExtensionIdKey, extension_id));
-
-  return return_value;
-}
-
-MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapFromPaths(
-    const std::vector<base::FilePath>& paths,
-    const std::string& extension_id,
-    const std::string& default_locale,
-    extension_l10n_util::GzippedMessagesPermission gzip_permission) {
-  MessageBundle::SubstitutionMap* return_value =
-      LoadNonLocalizedMessageBundleSubstitutionMap(extension_id);
-
-  // Touch disk only if extension is localized.
-  if (default_locale.empty())
-    return return_value;
-
-  std::string error;
-  for (const base::FilePath& path : paths) {
-    std::unique_ptr<MessageBundle> bundle(
-        LoadMessageBundle(path, default_locale, gzip_permission, &error));
-    if (bundle) {
-      for (const auto& iter : *bundle->dictionary()) {
-        // |insert| only adds new entries, and does not replace entries in
-        // the main extension or previously processed imports.
-        return_value->insert(std::make_pair(iter.first, iter.second));
-      }
-    }
-  }
-
-  return return_value;
-}
-
 base::FilePath GetVerifiedContentsPath(const base::FilePath& extension_path) {
   return extension_path.Append(kMetadataFolder)
       .Append(kVerifiedContentsFilename);
diff --git a/extensions/common/file_util.h b/extensions/common/file_util.h
index 810c496..391b65e7 100644
--- a/extensions/common/file_util.h
+++ b/extensions/common/file_util.h
@@ -147,31 +147,6 @@
     extension_l10n_util::GzippedMessagesPermission gzip_permission,
     std::string* error);
 
-// Loads the extension message bundle substitution map. Contains at least
-// the extension_id item. Does not supported compressed locale files. Passes
-// |gzip_permission| to extension_l10n_util::LoadMessageCatalogs (see
-// extension_l10n_util.h).
-MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
-    const base::FilePath& extension_path,
-    const std::string& extension_id,
-    const std::string& default_locale,
-    extension_l10n_util::GzippedMessagesPermission gzip_permission);
-
-// Loads the extension message bundle substitution map for a non-localized
-// extension. Contains only the extension_id item.
-// This doesn't require hitting disk, so it's safe to call on any thread.
-MessageBundle::SubstitutionMap* LoadNonLocalizedMessageBundleSubstitutionMap(
-    const std::string& extension_id);
-
-// Loads the extension message bundle substitution map from the specified paths.
-// Contains at least the extension_id item. Passes |gzip_permission| to
-// extension_l10n_util::LoadMessageCatalogs (see extension_l10n_util.h).
-MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapFromPaths(
-    const std::vector<base::FilePath>& paths,
-    const std::string& extension_id,
-    const std::string& default_locale,
-    extension_l10n_util::GzippedMessagesPermission gzip_permission);
-
 // Helper functions for getting paths for files used in content verification.
 base::FilePath GetVerifiedContentsPath(const base::FilePath& extension_path);
 base::FilePath GetComputedHashesPath(const base::FilePath& extension_path);
diff --git a/extensions/common/mojom/event_dispatcher.mojom b/extensions/common/mojom/event_dispatcher.mojom
index 413fdd9..825a671 100644
--- a/extensions/common/mojom/event_dispatcher.mojom
+++ b/extensions/common/mojom/event_dispatcher.mojom
@@ -6,6 +6,17 @@
 
 import "mojo/public/mojom/base/values.mojom";
 import "url/mojom/url.mojom";
+// EventDispatch is a one-per-renderer-processs interface that is responsible
+// for triggering API events that extensions have registered listeners for in
+// the renderer (either the extension process renderer, for extension-origin
+// pages, or a web renderer, for content script listeners).
+// The interface is implemented in //extensions/renderer code, and is
+// called on the browser side by the EventRouter.
+interface EventDispatcher {
+    // Sent to the renderer to dispatch an event to a listener.
+    DispatchEvent(DispatchEventParams params,
+                  mojo_base.mojom.ListValue event_args);
+};
 
 // Typemapped to extensions::EventFilteringInfo.
 // TODO(yochio): Convert extensions::EventFilteringInfo usage to
diff --git a/extensions/common/mojom/event_dispatcher_mojom_traits.h b/extensions/common/mojom/event_dispatcher_mojom_traits.h
index fb2b9b5..9ba9ff3 100644
--- a/extensions/common/mojom/event_dispatcher_mojom_traits.h
+++ b/extensions/common/mojom/event_dispatcher_mojom_traits.h
@@ -27,7 +27,7 @@
     return filtering_info.instance_id.has_value();
   }
   static int instance_id(const extensions::EventFilteringInfo& filtering_info) {
-    return filtering_info.instance_id.value();
+    return filtering_info.instance_id.value_or(0);
   }
   static absl::optional<std::string> window_type(
       const extensions::EventFilteringInfo& filtering_info) {
@@ -39,7 +39,7 @@
   }
   static int window_exposed_by_default(
       const extensions::EventFilteringInfo& filtering_info) {
-    return filtering_info.window_exposed_by_default.value();
+    return filtering_info.window_exposed_by_default.value_or(0);
   }
 
   static bool Read(extensions::mojom::EventFilteringInfoDataView data,
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 4728e57..306ec23 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -276,7 +276,8 @@
       source_map_(&ui::ResourceBundle::GetSharedInstance()),
       v8_schema_registry_(new V8SchemaRegistry),
       activity_logging_enabled_(false),
-      receiver_(this) {
+      receiver_(this),
+      dispatcher_(this) {
   bindings_system_ = CreateBindingsSystem(
       IPCMessageSender::CreateMainThreadIPCMessageSender());
 
@@ -769,7 +770,7 @@
   // |frame_helper| and |render_frame| might be dead by now.
 }
 
-void Dispatcher::DispatchEvent(
+void Dispatcher::DispatchEventHelper(
     const std::string& extension_id,
     const std::string& event_name,
     const base::ListValue& event_args,
@@ -975,7 +976,6 @@
   IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage)
   IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect)
   IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, OnDispatchOnDisconnect)
-  IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchEvent, OnDispatchEvent)
   IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -992,11 +992,14 @@
   // never deleted).
   associated_interfaces->AddInterface(base::BindRepeating(
       &Dispatcher::OnRendererAssociatedRequest, base::Unretained(this)));
+  associated_interfaces->AddInterface(base::BindRepeating(
+      &Dispatcher::OnEventDispatcherRequest, base::Unretained(this)));
 }
 
 void Dispatcher::UnregisterMojoInterfaces(
     blink::AssociatedInterfaceRegistry* associated_interfaces) {
   associated_interfaces->RemoveInterface(mojom::Renderer::Name_);
+  associated_interfaces->RemoveInterface(mojom::EventDispatcher::Name_);
 }
 
 void Dispatcher::OnRendererAssociatedRequest(
@@ -1004,6 +1007,11 @@
   receiver_.Bind(std::move(receiver));
 }
 
+void Dispatcher::OnEventDispatcherRequest(
+    mojo::PendingAssociatedReceiver<mojom::EventDispatcher> dispatcher) {
+  dispatcher_.Bind(std::move(dispatcher));
+}
+
 void Dispatcher::ActivateExtension(const std::string& extension_id) {
   TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::ActivateExtension", extension_id);
 
@@ -1174,13 +1182,14 @@
   // the browser know when we are starting and stopping the event dispatch, so
   // that it still considers the extension idle despite any activity the suspend
   // event creates.
-  DispatchEvent(extension_id, kOnSuspendEvent, base::ListValue(), nullptr);
+  DispatchEventHelper(extension_id, kOnSuspendEvent, base::ListValue(),
+                      nullptr);
   std::move(callback).Run();
 }
 
 void Dispatcher::CancelSuspendExtension(const std::string& extension_id) {
-  DispatchEvent(extension_id, kOnSuspendCanceledEvent, base::ListValue(),
-                nullptr);
+  DispatchEventHelper(extension_id, kOnSuspendCanceledEvent, base::ListValue(),
+                      nullptr);
 }
 
 void Dispatcher::SetSystemFont(const std::string& font_family,
@@ -1296,10 +1305,10 @@
       NULL);  // All render frames.
 }
 
-void Dispatcher::OnDispatchEvent(const mojom::DispatchEventParams& params,
-                                 const base::ListValue& event_args) {
+void Dispatcher::DispatchEvent(mojom::DispatchEventParamsPtr params,
+                               base::Value event_args) {
   content::RenderFrame* background_frame =
-      ExtensionFrameHelper::GetBackgroundPageFrame(params.extension_id);
+      ExtensionFrameHelper::GetBackgroundPageFrame(params->extension_id);
 
   // Synthesize a user gesture if this was in response to user action; this is
   // necessary if the gesture was e.g. by clicking on the extension toolbar
@@ -1310,27 +1319,28 @@
   // the user gesture. This is intentional, since frames other than the
   // background page should have their own user gestures, such as through button
   // clicks.
-  if (params.is_user_gesture && background_frame) {
+  if (params->is_user_gesture && background_frame) {
     ScriptContext* background_context =
         ScriptContextSet::GetMainWorldContextForFrame(background_frame);
     if (background_context && bindings_system_->HasEventListenerInContext(
-                                  params.event_name, background_context)) {
+                                  params->event_name, background_context)) {
       background_frame->GetWebFrame()->NotifyUserActivation(
           blink::mojom::UserActivationNotificationType::kExtensionEvent);
     }
   }
 
-  DispatchEvent(params.extension_id, params.event_name, event_args,
-                mojom::EventFilteringInfo::From(params.filtering_info));
+  DispatchEventHelper(params->extension_id, params->event_name,
+                      base::Value::AsListValue(event_args),
+                      mojom::EventFilteringInfo::From(params->filtering_info));
 
   if (background_frame) {
     // Tell the browser process when an event has been dispatched with a lazy
     // background page active.
     const Extension* extension =
-        RendererExtensionRegistry::Get()->GetByID(params.extension_id);
+        RendererExtensionRegistry::Get()->GetByID(params->extension_id);
     if (extension && BackgroundInfo::HasLazyBackgroundPage(extension)) {
       background_frame->Send(new ExtensionHostMsg_EventAck(
-          background_frame->GetRoutingID(), params.event_id));
+          background_frame->GetRoutingID(), params->event_id));
     }
   }
 }
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index 35246a6..c8dcb1f 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -74,7 +74,8 @@
 // renderer extension related state.
 class Dispatcher : public content::RenderThreadObserver,
                    public UserScriptSetManager::Observer,
-                   public mojom::Renderer {
+                   public mojom::Renderer,
+                   public mojom::EventDispatcher {
  public:
   explicit Dispatcher(std::unique_ptr<DispatcherDelegate> delegate);
 
@@ -166,10 +167,10 @@
   void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame);
 
   // Dispatches the event named |event_name| to all render views.
-  void DispatchEvent(const std::string& extension_id,
-                     const std::string& event_name,
-                     const base::ListValue& event_args,
-                     mojom::EventFilteringInfoPtr filtering_info) const;
+  void DispatchEventHelper(const std::string& extension_id,
+                           const std::string& event_name,
+                           const base::ListValue& event_args,
+                           mojom::EventFilteringInfoPtr filtering_info) const;
 
   // Shared implementation of the various MessageInvoke IPCs.
   void InvokeModuleSystemMethod(content::RenderFrame* render_frame,
@@ -263,6 +264,8 @@
 
   void OnRendererAssociatedRequest(
       mojo::PendingAssociatedReceiver<mojom::Renderer> receiver);
+  void OnEventDispatcherRequest(
+      mojo::PendingAssociatedReceiver<mojom::EventDispatcher> receiver);
   void OnDeliverMessage(int worker_thread_id,
                         const PortId& target_port_id,
                         const Message& message);
@@ -274,8 +277,8 @@
   void OnDispatchOnDisconnect(int worker_thread_id,
                               const PortId& port_id,
                               const std::string& error_message);
-  void OnDispatchEvent(const mojom::DispatchEventParams& params,
-                       const base::ListValue& event_args);
+  void DispatchEvent(mojom::DispatchEventParamsPtr params,
+                     base::Value event_args) override;
 
   // UserScriptSetManager::Observer implementation.
   void OnUserScriptsUpdated(const mojom::HostID& changed_host) override;
@@ -371,6 +374,10 @@
   // it is dependent on other messages sent on other associated channels.
   mojo::AssociatedReceiver<mojom::Renderer> receiver_;
 
+  // Extensions Dipsatch receiver. This is an associated receiver because
+  // it is dependent on other messages sent on other associated channels.
+  mojo::AssociatedReceiver<mojom::EventDispatcher> dispatcher_;
+
   // Used to hold a service worker information which is ready to execute but the
   // onloaded message haven't been received yet. We need to defer service worker
   // execution until the ExtensionMsg_Loaded message is received because we can
diff --git a/google_apis/test/embedded_setup_chromeos.html b/google_apis/test/embedded_setup_chromeos.html
index e493484..1ad956b 100644
--- a/google_apis/test/embedded_setup_chromeos.html
+++ b/google_apis/test/embedded_setup_chromeos.html
@@ -69,6 +69,15 @@
       }, '/');
 };
 
+gaia.chromeOSLogin.confirmToken = function() {
+  // SAML credential passing api for password.
+  window.postMessage(
+      {type: 'gaia_saml_api',
+       call: {method: 'confirm',
+              token: 'token'}
+      }, '/');
+};
+
 gaia.chromeOSLogin.sendUserInfo = function(services) {
   msg = {
     'method': 'userInfo',
@@ -153,6 +162,8 @@
         }
         if (services)
           gaia.chromeOSLogin.sendUserInfo(services);
+
+        gaia.chromeOSLogin.confirmToken();
         if (gaia.chromeOSLogin.shouldSendCloseView)
           gaia.chromeOSLogin.sendCloseView();
         history.pushState({}, "", window.location.pathname + "#close");
diff --git a/infra/config/generated/builders/ci/android-web-platform-pie-x86-fyi-rel/properties.textpb b/infra/config/generated/builders/ci/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb
similarity index 100%
rename from infra/config/generated/builders/ci/android-web-platform-pie-x86-fyi-rel/properties.textpb
rename to infra/config/generated/builders/ci/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb
diff --git a/infra/config/generated/builders/try/android-web-platform-pie-x86-fyi-rel/properties.textpb b/infra/config/generated/builders/try/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb
similarity index 100%
rename from infra/config/generated/builders/try/android-web-platform-pie-x86-fyi-rel/properties.textpb
rename to infra/config/generated/builders/try/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index ff8cc06..52cfbcf 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -307,6 +307,10 @@
         location_regexp_exclude: ".+/[+]/infra/config/.+"
       }
       builders {
+        name: "chromium/try/android-chrome-pie-x86-wpt-fyi-rel"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/android-cronet-arm-dbg"
         location_regexp: ".+/[+]/components/cronet/.+"
         location_regexp: ".+/[+]/components/grpc_support/.+"
@@ -486,10 +490,6 @@
         includable_only: true
       }
       builders {
-        name: "chromium/try/android-web-platform-pie-x86-fyi-rel"
-        includable_only: true
-      }
-      builders {
         name: "chromium/try/android-weblayer-10-x86-rel-tests"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 47e1045..e5248bd 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -3412,7 +3412,7 @@
       }
     }
     builders {
-      name: "Comparison Linux"
+      name: "Comparison Linux (reclient)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cores:8"
@@ -3495,7 +3495,7 @@
       }
     }
     builders {
-      name: "Comparison Windows"
+      name: "Comparison Windows (reclient)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cores:32"
@@ -25074,6 +25074,89 @@
       }
     }
     builders {
+      name: "android-chrome-pie-x86-wpt-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.android.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-code-coverage"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -27721,89 +27804,6 @@
       }
     }
     builders {
-      name: "android-web-platform-pie-x86-fyi-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/android-web-platform-pie-x86-fyi-rel/properties.textpb",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.android.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "luci.use_realms"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "android-weblayer-10-x86-rel-tests"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -49295,6 +49295,100 @@
       }
     }
     builders {
+      name: "android-chrome-pie-x86-wpt-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/android-chrome-pie-x86-wpt-fyi-rel/properties.textpb",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.android",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-cronet-arm-dbg"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -52667,100 +52761,6 @@
       }
     }
     builders {
-      name: "android-web-platform-pie-x86-fyi-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/try/android-web-platform-pie-x86-fyi-rel/properties.textpb",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "tryserver.chromium.android",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium_trybot"'
-        '}'
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      grace_period {
-        seconds: 120
-      }
-      caches {
-        name: "win_toolchain"
-        path: "win_toolchain"
-      }
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      task_template_canary_percentage {
-        value: 5
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "luci.use_realms"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "try_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "android-weblayer-10-x86-rel-tests"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index a9edf8b..8b7242ca 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -3639,7 +3639,7 @@
     short_name: "P-WPT"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/android-web-platform-pie-x86-fyi-rel"
+    name: "buildbucket/luci.chromium.ci/android-chrome-pie-x86-wpt-fyi-rel"
     category: "builder_tester|web-platform"
     short_name: "P"
   }
@@ -6454,7 +6454,7 @@
     category: "linux"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/Comparison Linux"
+    name: "buildbucket/luci.chromium.ci/Comparison Linux (reclient)"
     category: "linux"
     short_name: "cmp"
   }
@@ -6641,7 +6641,7 @@
     short_name: "win"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/Comparison Windows"
+    name: "buildbucket/luci.chromium.ci/Comparison Windows (reclient)"
     category: "win"
     short_name: "re"
   }
@@ -14108,6 +14108,9 @@
     name: "buildbucket/luci.chromium.try/android-binary-size"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-chrome-pie-x86-wpt-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-cronet-arm-dbg"
   }
   builders {
@@ -14216,9 +14219,6 @@
     name: "buildbucket/luci.chromium.try/android-rust-arm-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-web-platform-pie-x86-fyi-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/android-weblayer-10-x86-rel-tests"
   }
   builders {
@@ -15231,6 +15231,9 @@
     name: "buildbucket/luci.chromium.try/android-binary-size"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-chrome-pie-x86-wpt-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-cronet-arm-dbg"
   }
   builders {
@@ -15333,9 +15336,6 @@
     name: "buildbucket/luci.chromium.try/android-pie-x86-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-web-platform-pie-x86-fyi-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/android-weblayer-10-x86-rel-tests"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 77d00ec6..4342227 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -692,23 +692,23 @@
   }
 }
 job {
-  id: "Comparison Linux"
+  id: "Comparison Linux (reclient)"
   realm: "ci"
   acl_sets: "ci"
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "Comparison Linux"
+    builder: "Comparison Linux (reclient)"
   }
 }
 job {
-  id: "Comparison Windows"
+  id: "Comparison Windows (reclient)"
   realm: "ci"
   acl_sets: "ci"
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "Comparison Windows"
+    builder: "Comparison Windows (reclient)"
   }
 }
 job {
@@ -4241,6 +4241,16 @@
   }
 }
 job {
+  id: "android-chrome-pie-x86-wpt-fyi-rel"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "android-chrome-pie-x86-wpt-fyi-rel"
+  }
+}
+job {
   id: "android-code-coverage"
   realm: "ci"
   schedule: "triggered"
@@ -4565,16 +4575,6 @@
   }
 }
 job {
-  id: "android-web-platform-pie-x86-fyi-rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "android-web-platform-pie-x86-fyi-rel"
-  }
-}
-job {
   id: "android-weblayer-10-x86-rel-tests"
   realm: "ci"
   acls {
@@ -6941,8 +6941,8 @@
   triggers: "ChromeOS FYI Release (amd64-generic)"
   triggers: "ChromeOS FYI Release (kevin)"
   triggers: "ChromiumOS ASAN Release"
-  triggers: "Comparison Linux"
-  triggers: "Comparison Windows"
+  triggers: "Comparison Linux (reclient)"
+  triggers: "Comparison Windows (reclient)"
   triggers: "CrWinAsan"
   triggers: "CrWinAsan(dll)"
   triggers: "Dawn Linux x64 Builder"
@@ -7101,6 +7101,7 @@
   triggers: "android-backuprefptr-arm64-fyi-rel"
   triggers: "android-bfcache-rel"
   triggers: "android-binary-size-generator"
+  triggers: "android-chrome-pie-x86-wpt-fyi-rel"
   triggers: "android-code-coverage-native"
   triggers: "android-cronet-arm-dbg"
   triggers: "android-cronet-arm-rel"
@@ -7120,7 +7121,6 @@
   triggers: "android-pie-arm64-wpt-rel-non-cq"
   triggers: "android-pie-x86-rel"
   triggers: "android-rust-arm-rel"
-  triggers: "android-web-platform-pie-x86-fyi-rel"
   triggers: "android-weblayer-pie-x86-wpt-fyi-rel"
   triggers: "android-weblayer-pie-x86-wpt-smoketest"
   triggers: "android-weblayer-with-aosp-webview-x86-fyi-rel"
diff --git a/infra/config/lib/bootstrap.star b/infra/config/lib/bootstrap.star
index cde3be8..8ac7abe 100644
--- a/infra/config/lib/bootstrap.star
+++ b/infra/config/lib/bootstrap.star
@@ -163,7 +163,7 @@
             if bootstrap_node.props.bootstrap:
                 non_bootstrapped_properties.update({
                     "$bootstrap/exe": {
-                        "exe": builder.exe,
+                        "exe": json.decode(proto.to_jsonpb(builder.exe, use_proto_names = True)),
                     },
                     "led_builder_is_bootstrapped": True,
                 })
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
index d2eaf884..e96a3c1 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
@@ -48,7 +48,7 @@
 )
 
 ci.builder(
-    name = "android-web-platform-pie-x86-fyi-rel",
+    name = "android-chrome-pie-x86-wpt-fyi-rel",
     console_view_entry = consoles.console_view_entry(
         category = "builder_tester|web-platform",
         short_name = "P",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 791071e..f77ccad 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -595,7 +595,7 @@
 )
 
 ci.builder(
-    name = "Comparison Linux",
+    name = "Comparison Linux (reclient)",
     console_view_entry = consoles.console_view_entry(
         category = "linux",
         short_name = "cmp",
@@ -610,7 +610,7 @@
 )
 
 ci.builder(
-    name = "Comparison Windows",
+    name = "Comparison Windows (reclient)",
     builderless = True,
     console_view_entry = consoles.console_view_entry(
         category = "win",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index db4f207..6a0f7de10e 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -329,7 +329,7 @@
 )
 
 try_.builder(
-    name = "android-web-platform-pie-x86-fyi-rel",
+    name = "android-chrome-pie-x86-wpt-fyi-rel",
 )
 
 try_.builder(
diff --git a/ios/chrome/browser/infobars/overlays/BUILD.gn b/ios/chrome/browser/infobars/overlays/BUILD.gn
index 563858ac..c949e70 100644
--- a/ios/chrome/browser/infobars/overlays/BUILD.gn
+++ b/ios/chrome/browser/infobars/overlays/BUILD.gn
@@ -38,6 +38,7 @@
     "//ios/chrome/browser/overlays/public/common/infobars",
     "//ios/chrome/browser/overlays/public/infobar_banner",
     "//ios/chrome/browser/overlays/public/infobar_modal",
+    "//ios/chrome/browser/overlays/public/infobar_modal/permissions",
     "//ios/chrome/browser/passwords:infobar_delegates",
     "//ios/web/public",
   ]
diff --git a/ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.mm b/ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.mm
index bf6a073e..055f691 100644
--- a/ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.mm
+++ b/ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.mm
@@ -17,6 +17,7 @@
 #import "ios/chrome/browser/overlays/public/infobar_banner/translate_infobar_banner_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/public/infobar_banner/update_password_infobar_banner_overlay.h"
 #import "ios/chrome/browser/overlays/public/infobar_modal/password_infobar_modal_overlay_request_config.h"
+#import "ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/public/infobar_modal/reading_list_modal_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/public/infobar_modal/save_card_infobar_modal_overlay_request_config.h"
@@ -140,6 +141,9 @@
         case InfobarOverlayType::kBanner:
           return OverlayRequest::CreateWithConfig<
               PermissionsBannerRequestConfig>(infobar_ios);
+        case InfobarOverlayType::kModal:
+          return OverlayRequest::CreateWithConfig<
+              PermissionsInfobarModalOverlayRequestConfig>(infobar_ios);
         default:
           return nullptr;
       }
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn
new file mode 100644
index 0000000..26486d12
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("permissions") {
+  sources = [
+    "permissions_modal_overlay_request_config.h",
+    "permissions_modal_overlay_request_config.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+    "//ios/chrome/browser/infobars",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/common/infobars",
+  ]
+}
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h
new file mode 100644
index 0000000..7af14ef
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium 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_OVERLAYS_PUBLIC_INFOBAR_MODAL_PERMISSIONS_PERMISSIONS_MODAL_OVERLAY_REQUEST_CONFIG_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_PERMISSIONS_PERMISSIONS_MODAL_OVERLAY_REQUEST_CONFIG_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "ios/chrome/browser/overlays/public/overlay_request_config.h"
+
+class InfoBarIOS;
+
+// Configuration object for OverlayRequests for the modal UI for an infobar.
+class PermissionsInfobarModalOverlayRequestConfig
+    : public OverlayRequestConfig<PermissionsInfobarModalOverlayRequestConfig> {
+ public:
+  ~PermissionsInfobarModalOverlayRequestConfig() override;
+
+  // The associated web state.
+  web::WebState* GetWebState() const;
+
+ private:
+  OVERLAY_USER_DATA_SETUP(PermissionsInfobarModalOverlayRequestConfig);
+  explicit PermissionsInfobarModalOverlayRequestConfig(InfoBarIOS* infobar);
+
+  // OverlayUserData:
+  void CreateAuxiliaryData(base::SupportsUserData* user_data) override;
+
+  // The InfoBar causing this modal.
+  InfoBarIOS* infobar_ = nullptr;
+
+  web::WebState* web_state_ = nullptr;
+};
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_PERMISSIONS_PERMISSIONS_MODAL_OVERLAY_REQUEST_CONFIG_H_
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm
new file mode 100644
index 0000000..01a271af
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium 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/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h"
+
+#include "ios/chrome/browser/infobars/infobar_ios.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+OVERLAY_USER_DATA_SETUP_IMPL(PermissionsInfobarModalOverlayRequestConfig);
+
+PermissionsInfobarModalOverlayRequestConfig::
+    PermissionsInfobarModalOverlayRequestConfig(InfoBarIOS* infobar)
+    : infobar_(infobar) {
+  DCHECK(infobar_);
+  // TODO(crbug.com/1289645): Retrieve the current webstate from the permissions
+  // delegate once implemented.
+}
+
+PermissionsInfobarModalOverlayRequestConfig::
+    ~PermissionsInfobarModalOverlayRequestConfig() = default;
+
+web::WebState* PermissionsInfobarModalOverlayRequestConfig::GetWebState()
+    const {
+  return web_state_;
+}
+
+void PermissionsInfobarModalOverlayRequestConfig::CreateAuxiliaryData(
+    base::SupportsUserData* user_data) {
+  InfobarOverlayRequestConfig::CreateForUserData(
+      user_data, infobar_, InfobarOverlayType::kModal, false);
+}
diff --git a/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
index 7c884d33e..3378499 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
@@ -1703,11 +1703,6 @@
   ItemType itemType =
       static_cast<ItemType>([model itemTypeForIndexPath:indexPath]);
   switch (itemType) {
-    case ItemTypeLinkHeader:
-    case ItemTypeHeader:
-    case ItemTypeSavePasswordsSwitch:
-    case ItemTypeManagedSavePasswords:
-      break;
     case ItemTypePasswordsInOtherApps:
       [self.handler showPasswordsInOtherAppsPromo];
       break;
@@ -1758,8 +1753,12 @@
       BlockToOpenURL(self, self.dispatcher)(url);
       break;
     }
-    case ItemTypeOnDeviceEncryptionOptInDescription:
     case ItemTypeLastCheckTimestampFooter:
+    case ItemTypeOnDeviceEncryptionOptInDescription:
+    case ItemTypeLinkHeader:
+    case ItemTypeHeader:
+    case ItemTypeSavePasswordsSwitch:
+    case ItemTypeManagedSavePasswords:
       NOTREACHED();
   }
   [tableView deselectRowAtIndexPath:indexPath animated:YES];
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 7846893..0e1adf8 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-c2d83790d17590989d1efe9d66ecd67ee201785e
\ No newline at end of file
+d72acc13503097754c30dab2d7008b89d9aa1897
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 653acee..e6d3e66 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-240cc33f62e0945d83587979527706092caa045f
\ No newline at end of file
+aafd9a62f4ea760d4f15ace9dc2be1204b34dfa2
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 52f2d3f..2799784 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-fbfbc9bfc286a6ae08944b69c94c20029f3bc00b
\ No newline at end of file
+202196c9ef7fee42d70bf7ffe9b80677b876ae91
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index a2f0a17..679a5ff 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-63e5c65b42f4c878baf969d0491b5f0339fae225
\ No newline at end of file
+285816d1d62fe596af9603ea6a37d5ff8b16859c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 8fb3030..088934b 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-2c1025f79ce5ad5d626c4fde0a6b843e9506ef1b
\ No newline at end of file
+fef5205011dd9bf55dac4e98d4c29790dddcfec6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 7e5426d..e051588 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-16e7f46bbe0b506d2e6bb0fdbd76e38857b14fc3
\ No newline at end of file
+f3503a4a3161f581601e99a7172f39e23a58cc33
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 82046088..19c998a7 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-9d3f8844c5d73e34beabea01a98c194a9e9a04c6
\ No newline at end of file
+946f5f5fbb5973e7f685555f042723182fa0ba61
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index a1977ef..0ad6ed0b 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-78e8fa82d2e0cab7642df83257ba41d301c0b1cf
\ No newline at end of file
+2912bdbd8f1fad18ec8a4beb13bb0f492b65a478
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 3136959b..eaabffb 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-282cb5a6721879c733cb2bcdd61520605bdaa452
\ No newline at end of file
+037e8e86ee27b9b62a88428c4ec59737be6c9d4a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 57dfda0..f4fb1dd 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-4e9100e23c75a9f40f62681deb749706162cb2eb
\ No newline at end of file
+4dc5165d2137baae13e945d92a8d9ff98c753330
\ No newline at end of file
diff --git a/media/base/decoder_status.cc b/media/base/decoder_status.cc
index 869b3bc..9fffd5f 100644
--- a/media/base/decoder_status.cc
+++ b/media/base/decoder_status.cc
@@ -22,6 +22,7 @@
     STRINGIFY(DecoderStatus::Codes::kAborted);
     STRINGIFY(DecoderStatus::Codes::kInvalidArgument);
     STRINGIFY(DecoderStatus::Codes::kInterrupted);
+    STRINGIFY(DecoderStatus::Codes::kDisconnected);
     STRINGIFY(DecoderStatus::Codes::kNotInitialized);
     STRINGIFY(DecoderStatus::Codes::kMissingCDM);
     STRINGIFY(DecoderStatus::Codes::kFailedToGetVideoFrame);
diff --git a/media/base/decoder_status.h b/media/base/decoder_status.h
index 5618777..edd2de6 100644
--- a/media/base/decoder_status.h
+++ b/media/base/decoder_status.h
@@ -18,6 +18,7 @@
     kAborted = 2,  // TODO(*) document _why_ aborted is a thing
     kInvalidArgument = 3,
     kInterrupted = 4,
+    kDisconnected = 5,  // Lost mojo connection, e.g remote crashed or teardown
 
     // Reasons for failing to decode
     kNotInitialized = 100,
diff --git a/media/mojo/clients/mojo_audio_decoder.cc b/media/mojo/clients/mojo_audio_decoder.cc
index 60d2109..8855de0 100644
--- a/media/mojo/clients/mojo_audio_decoder.cc
+++ b/media/mojo/clients/mojo_audio_decoder.cc
@@ -70,7 +70,7 @@
   // This could happen during reinitialization.
   if (!remote_decoder_.is_connected()) {
     DVLOG(1) << __func__ << ": Connection error happened.";
-    FailInit(std::move(init_cb), DecoderStatus::Codes::kFailedToCreateDecoder);
+    FailInit(std::move(init_cb), DecoderStatus::Codes::kDisconnected);
     return;
   }
 
@@ -103,9 +103,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!remote_decoder_.is_connected()) {
-    task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(decode_cb),
+                                          DecoderStatus::Codes::kDisconnected));
     return;
   }
 
@@ -133,8 +133,8 @@
   if (!remote_decoder_.is_connected()) {
     if (decode_cb_) {
       task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kFailed));
+          FROM_HERE, base::BindOnce(std::move(decode_cb_),
+                                    DecoderStatus::Codes::kDisconnected));
     }
 
     task_runner_->PostTask(FROM_HERE, std::move(closure));
@@ -188,12 +188,12 @@
   DCHECK(!remote_decoder_.is_connected());
 
   if (init_cb_) {
-    FailInit(std::move(init_cb_), DecoderStatus::Codes::kFailedToCreateDecoder);
+    FailInit(std::move(init_cb_), DecoderStatus::Codes::kDisconnected);
     return;
   }
 
   if (decode_cb_)
-    std::move(decode_cb_).Run(DecoderStatus::Codes::kFailed);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kDisconnected);
   if (reset_cb_)
     std::move(reset_cb_).Run();
 }
diff --git a/media/mojo/clients/mojo_video_decoder.cc b/media/mojo/clients/mojo_video_decoder.cc
index 9f860be..bc6b09e 100644
--- a/media/mojo/clients/mojo_video_decoder.cc
+++ b/media/mojo/clients/mojo_video_decoder.cc
@@ -214,7 +214,7 @@
     absl::optional<base::UnguessableToken> cdm_id) {
   if (has_connection_error_) {
     DCHECK(init_cb_);
-    FailInit(std::move(init_cb_), DecoderStatus::Codes::kFailedToCreateDecoder);
+    FailInit(std::move(init_cb_), DecoderStatus::Codes::kDisconnected);
     return;
   }
 
@@ -244,9 +244,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (has_connection_error_) {
-    task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb),
-                                  DecoderStatus::Codes::kNotInitialized));
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(decode_cb),
+                                          DecoderStatus::Codes::kDisconnected));
     return;
   }
 
@@ -495,14 +495,14 @@
   base::WeakPtr<MojoVideoDecoder> weak_this = weak_this_;
 
   if (init_cb_)
-    std::move(init_cb_).Run(DecoderStatus::Codes::kFailedToCreateDecoder);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kDisconnected);
 
   if (!weak_this)
     return;
 
   for (auto& pending_decode : pending_decodes_) {
     // It would be ideal if we could get a reason for the interruption.
-    std::move(pending_decode.second).Run(DecoderStatus::Codes::kInterrupted);
+    std::move(pending_decode.second).Run(DecoderStatus::Codes::kDisconnected);
     if (!weak_this)
       return;
   }
diff --git a/media/mojo/clients/win/media_foundation_renderer_client.cc b/media/mojo/clients/win/media_foundation_renderer_client.cc
index 3144f637..49cc4f65 100644
--- a/media/mojo/clients/win/media_foundation_renderer_client.cc
+++ b/media/mojo/clients/win/media_foundation_renderer_client.cc
@@ -288,19 +288,18 @@
       mojo::WrapCallbackWithDefaultInvokeIfNotRun(
           base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceReceived,
                          weak_factory_.GetWeakPtr()),
-          absl::nullopt));
+          absl::nullopt, "disconnection error"));
 }
 
 void MediaFoundationRendererClient::OnDCOMPSurfaceReceived(
-    const absl::optional<base::UnguessableToken>& token) {
+    const absl::optional<base::UnguessableToken>& token,
+    const std::string& error) {
   DVLOG_FUNC(1);
   DCHECK(has_video_);
   DCHECK(media_task_runner_->BelongsToCurrentThread());
 
   if (!token) {
-    MEDIA_LOG(ERROR, media_log_)
-        << "Failed to initialize DCOMP mode or failed to get or "
-           "register DCOMP surface handle on remote renderer";
+    MEDIA_LOG(ERROR, media_log_) << "GetDCOMPSurface failed: " + error;
     MediaFoundationRenderer::ReportErrorReason(
         MediaFoundationRenderer::ErrorReason::kOnDCompSurfaceReceivedError);
     OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
diff --git a/media/mojo/clients/win/media_foundation_renderer_client.h b/media/mojo/clients/win/media_foundation_renderer_client.h
index 26b1bf0f..608e7cb 100644
--- a/media/mojo/clients/win/media_foundation_renderer_client.h
+++ b/media/mojo/clients/win/media_foundation_renderer_client.h
@@ -92,7 +92,8 @@
   void OnSetOutputRectDone(const gfx::Size& output_size, bool success);
   void InitializeDCOMPRenderingIfNeeded();
   void OnDCOMPSurfaceReceived(
-      const absl::optional<base::UnguessableToken>& token);
+      const absl::optional<base::UnguessableToken>& token,
+      const std::string& error);
   void OnDCOMPSurfaceHandleSet(bool success);
   void OnVideoFrameCreated(scoped_refptr<VideoFrame> video_frame);
   void OnCdmAttached(bool success);
diff --git a/media/mojo/mojom/renderer_extensions.mojom b/media/mojo/mojom/renderer_extensions.mojom
index 9672c15..0cc5f1b6 100644
--- a/media/mojo/mojom/renderer_extensions.mojom
+++ b/media/mojo/mojom/renderer_extensions.mojom
@@ -58,8 +58,9 @@
 interface MediaFoundationRendererExtension {
   // Enables Direct Composition video rendering and gets the token associated
   // with the Direct Composition surface handle, which can be retrieved later
-  // in the GPU process using the token. Returns a null token on failures.
-  GetDCOMPSurface() => (mojo_base.mojom.UnguessableToken? token);
+  // in the GPU process using the token. Returns a null `token` on failures when
+  // `error` explains the failure reason.
+  GetDCOMPSurface() => (mojo_base.mojom.UnguessableToken? token, string error);
 
   // Notifies whether video is enabled.
   SetVideoStreamEnabled(bool enabled);
diff --git a/media/mojo/services/media_foundation_renderer_wrapper.cc b/media/mojo/services/media_foundation_renderer_wrapper.cc
index 9cb3dd5..4d8f85cc 100644
--- a/media/mojo/services/media_foundation_renderer_wrapper.cc
+++ b/media/mojo/services/media_foundation_renderer_wrapper.cc
@@ -131,9 +131,10 @@
 
 void MediaFoundationRendererWrapper::OnReceiveDCOMPSurface(
     GetDCOMPSurfaceCallback callback,
-    base::win::ScopedHandle handle) {
+    base::win::ScopedHandle handle,
+    const std::string& error) {
   if (!handle.IsValid()) {
-    std::move(callback).Run(absl::nullopt);
+    std::move(callback).Run(absl::nullopt, "invalid handle: " + error);
     return;
   }
 
@@ -155,12 +156,15 @@
 void MediaFoundationRendererWrapper::OnDCOMPSurfaceHandleRegistered(
     GetDCOMPSurfaceCallback callback,
     const absl::optional<base::UnguessableToken>& token) {
+  std::string error;
   if (token) {
     DCHECK(dcomp_surface_token_.is_empty());
     dcomp_surface_token_ = token.value();
+  } else {
+    error = "dcomp surface handle registration failed";
   }
 
-  std::move(callback).Run(token);
+  std::move(callback).Run(token, error);
 }
 
 }  // namespace media
diff --git a/media/mojo/services/media_foundation_renderer_wrapper.h b/media/mojo/services/media_foundation_renderer_wrapper.h
index 2678ec6..a0f5178f4 100644
--- a/media/mojo/services/media_foundation_renderer_wrapper.h
+++ b/media/mojo/services/media_foundation_renderer_wrapper.h
@@ -65,7 +65,8 @@
 
  private:
   void OnReceiveDCOMPSurface(GetDCOMPSurfaceCallback callback,
-                             base::win::ScopedHandle handle);
+                             base::win::ScopedHandle handle,
+                             const std::string& error);
   void OnDCOMPSurfaceHandleRegistered(
       GetDCOMPSurfaceCallback callback,
       const absl::optional<base::UnguessableToken>& token);
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc
index 3e16d803..6db95b4 100644
--- a/media/renderers/audio_renderer_impl.cc
+++ b/media/renderers/audio_renderer_impl.cc
@@ -842,10 +842,13 @@
   pending_read_ = false;
 
   if (result.has_error()) {
-    HandleAbortedReadOrDecodeError(result.code() ==
-                                           DecoderStatus::Codes::kAborted
-                                       ? PIPELINE_OK
-                                       : PIPELINE_ERROR_DECODE);
+    auto status = PIPELINE_ERROR_DECODE;
+    if (result.code() == DecoderStatus::Codes::kAborted)
+      status = PIPELINE_OK;
+    else if (result.code() == DecoderStatus::Codes::kDisconnected)
+      status = PIPELINE_ERROR_DISCONNECTED;
+
+    HandleAbortedReadOrDecodeError(status);
     return;
   }
 
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index 8caba70..a6301e4 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -573,12 +573,14 @@
       // However, since it's a dcheck, this seems okay.
       return;
     default:
-      DCHECK(result.has_error());
       // Anything other than `kOk` or `kAborted` is treated as an error.
+      DCHECK(result.has_error());
+      auto status = result.code() == DecoderStatus::Codes::kDisconnected
+                        ? PIPELINE_ERROR_DISCONNECTED
+                        : PIPELINE_ERROR_DECODE;
       task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&VideoRendererImpl::OnPlaybackError,
-                         weak_factory_.GetWeakPtr(), PIPELINE_ERROR_DECODE));
+          FROM_HERE, base::BindOnce(&VideoRendererImpl::OnPlaybackError,
+                                    weak_factory_.GetWeakPtr(), status));
       return;
   }
 
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index 523d037c..b88f8c13 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -69,6 +69,25 @@
   return true;
 }
 
+const std::string GetErrorReasonString(
+    const MediaFoundationRenderer::ErrorReason& reason) {
+#define STRINGIFY(value)                            \
+  case MediaFoundationRenderer::ErrorReason::value: \
+    return #value
+  switch (reason) {
+    STRINGIFY(kUnknown);
+    STRINGIFY(kCdmProxyReceivedInInvalidState);
+    STRINGIFY(kFailedToSetSourceOnMediaEngine);
+    STRINGIFY(kFailedToSetCurrentTime);
+    STRINGIFY(kFailedToPlay);
+    STRINGIFY(kOnPlaybackError);
+    STRINGIFY(kOnDCompSurfaceReceivedError);
+    STRINGIFY(kOnDCompSurfaceHandleSetError);
+    STRINGIFY(kOnConnectionError);
+  }
+#undef STRINGIFY
+}
+
 }  // namespace
 
 // static
@@ -375,9 +394,8 @@
   DVLOG_FUNC(1);
 
   if (!waiting_for_mf_cdm_ || !content_protection_manager_) {
-    DLOG(ERROR) << "Failed in checking internal state.";
-    ReportErrorReason(ErrorReason::kCdmProxyReceivedInInvalidState);
-    renderer_client_->OnError(PIPELINE_ERROR_INVALID_STATE);
+    OnError(PIPELINE_ERROR_INVALID_STATE,
+            ErrorReason::kCdmProxyReceivedInInvalidState);
     return;
   }
 
@@ -388,9 +406,8 @@
 
   HRESULT hr = SetSourceOnMediaEngine();
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to set source on media engine: " << PrintHr(hr);
-    ReportErrorReason(ErrorReason::kFailedToSetSourceOnMediaEngine);
-    renderer_client_->OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
+    OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
+            ErrorReason::kFailedToSetSourceOnMediaEngine, hr);
     return;
   }
 }
@@ -423,17 +440,14 @@
   // MF_MEDIA_ENGINE_EVENT_SEEKED event.
   HRESULT hr = mf_media_engine_->SetCurrentTime(current_time);
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to SetCurrentTime: " << PrintHr(hr);
-    ReportErrorReason(ErrorReason::kFailedToSetCurrentTime);
-    renderer_client_->OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
+    OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
+            ErrorReason::kFailedToSetCurrentTime, hr);
     return;
   }
 
   hr = mf_media_engine_->Play();
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to start playback: " << PrintHr(hr);
-    ReportErrorReason(ErrorReason::kFailedToPlay);
-    renderer_client_->OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
+    OnError(PIPELINE_ERROR_COULD_NOT_RENDER, ErrorReason::kFailedToPlay, hr);
     return;
   }
 }
@@ -451,16 +465,18 @@
 
   HRESULT hr = SetDCompModeInternal();
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to set DComp mode: " << PrintHr(hr);
-    std::move(callback).Run(base::win::ScopedHandle());
+    std::string error = "Failed to set DComp mode: " + PrintHr(hr);
+    DLOG(ERROR) << error;
+    std::move(callback).Run(base::win::ScopedHandle(), error);
     return;
   }
 
   HANDLE surface_handle = INVALID_HANDLE_VALUE;
   hr = GetDCompSurfaceInternal(&surface_handle);
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to get DComp surface: " << PrintHr(hr);
-    std::move(callback).Run(base::win::ScopedHandle());
+    std::string error = "Failed to get DComp surface: " + PrintHr(hr);
+    DLOG(ERROR) << error;
+    std::move(callback).Run(base::win::ScopedHandle(), error);
     return;
   }
 
@@ -472,12 +488,14 @@
       process, surface_handle, process, &duplicated_handle,
       GENERIC_READ | GENERIC_EXECUTE, false, DUPLICATE_CLOSE_SOURCE);
   if (!result) {
-    DLOG(ERROR) << "Duplicate surface_handle failed: " << ::GetLastError();
-    std::move(callback).Run(base::win::ScopedHandle());
+    std::string error =
+        "Duplicate surface_handle failed: " + PrintHr(::GetLastError());
+    DLOG(ERROR) << error;
+    std::move(callback).Run(base::win::ScopedHandle(), error);
     return;
   }
 
-  std::move(callback).Run(base::win::ScopedHandle(duplicated_handle));
+  std::move(callback).Run(base::win::ScopedHandle(duplicated_handle), "");
 }
 
 // TODO(crbug.com/1070030): Investigate if we need to add
@@ -658,21 +676,16 @@
   if (status == PIPELINE_ERROR_HARDWARE_CONTEXT_RESET && cdm_proxy_)
     cdm_proxy_->OnHardwareContextReset();
 
-  MEDIA_LOG(ERROR, media_log_)
-      << "MediaFoundationRenderer OnPlaybackError: " << status << ", "
-      << PrintHr(hr);
-
-  ReportErrorReason(ErrorReason::kOnPlaybackError);
-  renderer_client_->OnError(status);
   StopSendingStatistics();
+  OnError(status, ErrorReason::kOnPlaybackError, hr);
 }
 
 void MediaFoundationRenderer::OnPlaybackEnded() {
   DVLOG_FUNC(2);
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
-  renderer_client_->OnEnded();
   StopSendingStatistics();
+  renderer_client_->OnEnded();
 }
 
 void MediaFoundationRenderer::OnFormatChange() {
@@ -790,4 +803,16 @@
   renderer_client_->OnVideoNaturalSizeChange(native_video_size_);
 }
 
+void MediaFoundationRenderer::OnError(PipelineStatus status,
+                                      ErrorReason reason,
+                                      absl::optional<HRESULT> hresult) {
+  const std::string error =
+      "MediaFoundationRenderer error: " + GetErrorReasonString(reason) +
+      (hresult.has_value() ? (" (" + PrintHr(hresult.value()) + ")") : "");
+  DLOG(ERROR) << error;
+  MEDIA_LOG(ERROR, media_log_) << error;
+  ReportErrorReason(reason);
+  renderer_client_->OnError(status);
+}
+
 }  // namespace media
diff --git a/media/renderers/win/media_foundation_renderer.h b/media/renderers/win/media_foundation_renderer.h
index 64b20d7..5a1805c8 100644
--- a/media/renderers/win/media_foundation_renderer.h
+++ b/media/renderers/win/media_foundation_renderer.h
@@ -122,6 +122,9 @@
   HRESULT PauseInternal();
   HRESULT InitializeTexturePool(const gfx::Size& size);
   void OnVideoNaturalSizeChange();
+  void OnError(PipelineStatus status,
+               ErrorReason reason,
+               absl::optional<HRESULT> hresult = absl::nullopt);
 
   // Renderer methods are running in the same sequence.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/media/renderers/win/media_foundation_renderer_extension.h b/media/renderers/win/media_foundation_renderer_extension.h
index 742aa5e..b1c7353 100644
--- a/media/renderers/win/media_foundation_renderer_extension.h
+++ b/media/renderers/win/media_foundation_renderer_extension.h
@@ -23,8 +23,9 @@
   // method names in a separate CL.
 
   // Enables Direct Composition video rendering and returns the Direct
-  // Composition Surface handle.
-  using GetDCompSurfaceCB = base::OnceCallback<void(base::win::ScopedHandle)>;
+  // Composition Surface handle. On failure, `error` explains the error reason.
+  using GetDCompSurfaceCB = base::OnceCallback<void(base::win::ScopedHandle,
+                                                    const std::string& error)>;
   virtual void GetDCompSurface(GetDCompSurfaceCB callback) = 0;
 
   // Notifies renderer whether video is enabled.
diff --git a/media/renderers/win/media_foundation_renderer_unittest.cc b/media/renderers/win/media_foundation_renderer_unittest.cc
index 1e5b46a..6e12a66 100644
--- a/media/renderers/win/media_foundation_renderer_unittest.cc
+++ b/media/renderers/win/media_foundation_renderer_unittest.cc
@@ -233,7 +233,7 @@
   EXPECT_CALL(renderer_init_cb_, Run(HasStatusCode(PIPELINE_OK)));
   // Ignore the DirectComposition handle value returned as our |pmp_server_|
   // has no real implementation.
-  EXPECT_CALL(get_dcomp_surface_cb, Run(_));
+  EXPECT_CALL(get_dcomp_surface_cb, Run(_, _));
 
   mf_renderer_->Initialize(&media_resource_, &renderer_client_,
                            renderer_init_cb_.Get());
diff --git a/net/cookies/cookie_access_delegate.cc b/net/cookies/cookie_access_delegate.cc
index 00e03e6..2fe07c5 100644
--- a/net/cookies/cookie_access_delegate.cc
+++ b/net/cookies/cookie_access_delegate.cc
@@ -28,7 +28,10 @@
     const CookieAccessDelegate* delegate,
     const CookiePartitionKey& cookie_partition_key,
     base::OnceCallback<void(absl::optional<CookiePartitionKey>)> callback) {
-  if (!delegate) {
+  // FirstPartySetify doesn't need to transform partition keys with a nonce,
+  // since those partitions are only available to a single fenced/anonymous
+  // iframe.
+  if (!delegate || cookie_partition_key.nonce()) {
     std::move(callback).Run(cookie_partition_key);
     return;
   }
diff --git a/net/cookies/cookie_partition_key.h b/net/cookies/cookie_partition_key.h
index 46f0289..60ff48b 100644
--- a/net/cookies/cookie_partition_key.h
+++ b/net/cookies/cookie_partition_key.h
@@ -72,7 +72,7 @@
   // which were already created using Deserialize or FromNetworkIsolationKey.
   static CookiePartitionKey FromWire(
       const SchemefulSite& site,
-      absl::optional<base::UnguessableToken> nonce) {
+      absl::optional<base::UnguessableToken> nonce = absl::nullopt) {
     return CookiePartitionKey(site, nonce);
   }
 
diff --git a/net/cookies/cookie_partition_key_collection.cc b/net/cookies/cookie_partition_key_collection.cc
index 778a3986..0058f69 100644
--- a/net/cookies/cookie_partition_key_collection.cc
+++ b/net/cookies/cookie_partition_key_collection.cc
@@ -55,8 +55,16 @@
   std::vector<SchemefulSite> sites;
   sites.reserve(PartitionKeys().size());
   for (const CookiePartitionKey& key : PartitionKeys()) {
+    // Partition keys that have a nonce are not available across top-level sites
+    // in the same First-Party Set.
+    if (key.nonce())
+      continue;
     sites.push_back(key.site());
   }
+  if (sites.empty()) {
+    std::move(callback).Run(*this);
+    return;
+  }
   cookie_access_delegate->FindFirstPartySetOwners(
       sites,
       base::BindOnce(
@@ -69,9 +77,10 @@
               const auto first_party_set_owner_iter =
                   sites_to_owners.find(key.site());
               canonicalized_keys.push_back(
-                  first_party_set_owner_iter != sites_to_owners.end()
+                  !key.nonce() &&
+                          first_party_set_owner_iter != sites_to_owners.end()
                       ? CookiePartitionKey::FromWire(
-                            first_party_set_owner_iter->second, key.nonce())
+                            first_party_set_owner_iter->second)
                       : key);
             }
             std::move(callback).Run(
diff --git a/net/cookies/cookie_partition_key_collection_unittest.cc b/net/cookies/cookie_partition_key_collection_unittest.cc
index 29c585a..6b17db1f 100644
--- a/net/cookies/cookie_partition_key_collection_unittest.cc
+++ b/net/cookies/cookie_partition_key_collection_unittest.cc
@@ -160,6 +160,22 @@
                   &delegate)
                   .PartitionKeys(),
               UnorderedElementsAre(kOwnerPartitionKey, kNonMemberPartitionKey));
+
+  // Test that FirstPartySetify does not modify partition keys with nonces.
+  const CookiePartitionKey kNoncedPartitionKey =
+      CookiePartitionKey::FromURLForTesting(kMemberURL,
+                                            base::UnguessableToken::Create());
+  EXPECT_THAT(
+      FirstPartySetifyAndWait(
+          CookiePartitionKeyCollection({kNoncedPartitionKey}), &delegate)
+          .PartitionKeys(),
+      UnorderedElementsAre(kNoncedPartitionKey));
+  EXPECT_THAT(
+      FirstPartySetifyAndWait(CookiePartitionKeyCollection(
+                                  {kNoncedPartitionKey, kMemberPartitionKey}),
+                              &delegate)
+          .PartitionKeys(),
+      UnorderedElementsAre(kNoncedPartitionKey, kOwnerPartitionKey));
 }
 
 TEST(CookiePartitionKeyCollectionTest, Contains) {
diff --git a/ppapi/proxy/ppb_x509_certificate_private_proxy.cc b/ppapi/proxy/ppb_x509_certificate_private_proxy.cc
index 1d4c130..86107af 100644
--- a/ppapi/proxy/ppb_x509_certificate_private_proxy.cc
+++ b/ppapi/proxy/ppb_x509_certificate_private_proxy.cc
@@ -26,9 +26,6 @@
 
   bool ParseDER(const std::vector<char>& der,
                 PPB_X509Certificate_Fields* result) override;
-
- private:
-  void SendToBrowser(IPC::Message* msg);
 };
 
 X509CertificatePrivate::X509CertificatePrivate(PP_Instance instance)
@@ -44,10 +41,6 @@
                                                    result);
 }
 
-void X509CertificatePrivate::SendToBrowser(IPC::Message* msg) {
-  PluginGlobals::Get()->GetBrowserSender()->Send(msg);
-}
-
 }  // namespace
 
 //------------------------------------------------------------------------------
diff --git a/storage/browser/quota/quota_internals.mojom b/storage/browser/quota/quota_internals.mojom
index 7c0d2c4..afa27a7 100644
--- a/storage/browser/quota/quota_internals.mojom
+++ b/storage/browser/quota/quota_internals.mojom
@@ -5,7 +5,7 @@
 module storage.mojom;
 
 // Interface for controlling Quota Internals.
-// Hosted on chrome://quota-internals-2" for WebUI content::QuotaInternals2UI.
+// Hosted on chrome://quota-internals" for WebUI content::QuotaInternalsUI.
 interface QuotaInternalsHandler {
     // Returns the total and available disk space in bits for a user,
     // which is then converted to bytes and displayed on the Quota Internals UI.
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index 0dfea02d..dcf00634 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -695,7 +695,7 @@
   std::map<blink::StorageKey, QuotaOverride> devtools_overrides_;
   int next_override_handle_id_ = 0;
 
-  // Serve mojo connections for chrome://quota-internals-2 pages.
+  // Serve mojo connections for chrome://quota-internals pages.
   mojo::ReceiverSet<mojom::QuotaInternalsHandler> internals_handlers_receivers_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 58d4d6d3..260b947c 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -1772,7 +1772,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 9
+          "shards": 20
         },
         "test": "content_browsertests",
         "test_id_prefix": "ninja://content/test:content_browsertests/"
@@ -5305,51 +5305,7 @@
       }
     ]
   },
-  "android-pie-arm64-wpt-rel-non-cq": {
-    "isolated_scripts": [
-      {
-        "experiment_percentage": 100,
-        "isolate_name": "system_webview_wpt",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "system_webview_wpt",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "PQ3A.190801.002",
-              "device_os_flavor": "google",
-              "device_os_type": "userdebug",
-              "device_type": "walleye",
-              "os": "Android"
-            }
-          ],
-          "expiration": 18000,
-          "hard_timeout": 14400,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 25
-        },
-        "test_id_prefix": "ninja://android_webview/test:system_webview_wpt/"
-      }
-    ]
-  },
-  "android-web-platform-pie-x86-fyi-rel": {
+  "android-chrome-pie-x86-wpt-fyi-rel": {
     "isolated_scripts": [
       {
         "args": [
@@ -5415,6 +5371,50 @@
       }
     ]
   },
+  "android-pie-arm64-wpt-rel-non-cq": {
+    "isolated_scripts": [
+      {
+        "experiment_percentage": 100,
+        "isolate_name": "system_webview_wpt",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "system_webview_wpt",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "expiration": 18000,
+          "hard_timeout": 14400,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 25
+        },
+        "test_id_prefix": "ninja://android_webview/test:system_webview_wpt/"
+      }
+    ]
+  },
   "android-weblayer-10-x86-rel-tests": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 475219df..12f80c7b 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -40888,8 +40888,7 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android28.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_p.media_unittests.filter"
+          "--avd-config=../../tools/android/avd/proto/generic_android28.textpb"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 63a9a712..b17bbf0 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -1,12 +1,12 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "Comparison Linux": {
+  "Comparison Linux (reclient)": {
     "additional_compile_targets": [
       "all"
     ]
   },
-  "Comparison Windows": {
+  "Comparison Windows (reclient)": {
     "additional_compile_targets": [
       "all"
     ]
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index b48c0f0..2f40fe2 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -885,7 +885,7 @@
           "ignore_task_failure": false,
           "io_timeout": 21600,
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
+          "shards": 12
         },
         "trigger_script": {
           "args": [
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 5dd1993..8907008 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -328,7 +328,6 @@
   data = [
     "//testing/buildbot/filters/android.emulator_11_12.media_unittests.filter",
     "//testing/buildbot/filters/android.emulator_m.media_unittests.filter",
-    "//testing/buildbot/filters/android.emulator_p.media_unittests.filter",
     "//testing/buildbot/filters/fuchsia.debug.media_unittests.filter",
     "//testing/buildbot/filters/fuchsia.lsan.media_unittests.filter",
   ]
diff --git a/testing/buildbot/filters/android.emulator_m.media_unittests.filter b/testing/buildbot/filters/android.emulator_m.media_unittests.filter
index 5e21e2d..94ad9fc 100644
--- a/testing/buildbot/filters/android.emulator_m.media_unittests.filter
+++ b/testing/buildbot/filters/android.emulator_m.media_unittests.filter
@@ -1,8 +1,3 @@
-# https://crbug.com/1039873
--PaintCanvasVideoRendererWithGLTest.CopyVideoFrameTexturesToGLTextureI420
--PaintCanvasVideoRendererWithGLTest.PaintI420
--PaintCanvasVideoRendererWithGLTest.PaintI420NotSubset
-
 # https://crbug.com/1081506
 -AAudio/AudioOutputTest.Play200HzTone/0
 -AudioAndroidOutputTest.StartOutputStreamCallbacks
diff --git a/testing/buildbot/filters/android.emulator_p.media_unittests.filter b/testing/buildbot/filters/android.emulator_p.media_unittests.filter
deleted file mode 100644
index 1c01da1..0000000
--- a/testing/buildbot/filters/android.emulator_p.media_unittests.filter
+++ /dev/null
@@ -1,4 +0,0 @@
-# https://crbug.com/1039873
--PaintCanvasVideoRendererWithGLTest.CopyVideoFrameTexturesToGLTextureI420
--PaintCanvasVideoRendererWithGLTest.PaintI420
--PaintCanvasVideoRendererWithGLTest.PaintI420NotSubset
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 9a06c5e..d9f6216 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1297,6 +1297,11 @@
           ],
         },
       },
+      'android-12-x64-fyi-rel': {
+        'swarming': {
+          'shards': 20,
+        },
+      },
       'android-arm64-proguard-rel': {
         'swarming': {
           'shards': 16,
@@ -2112,11 +2117,6 @@
           '--gtest_filter=-AAudio/AudioOutputTest.Play200HzTone/0', # https://crbug.com/1034009
         ],
       },
-      'android-pie-x86-rel': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_p.media_unittests.filter',
-        ],
-      },
       'fuchsia-code-coverage': {
         'swarming': {
           'shards': 3,
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 509ff5e4..0801675 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1155,19 +1155,7 @@
           },
           'os_type': 'android',
       },
-      'android-pie-arm64-wpt-rel-non-cq': {
-        'mixins': [
-          'has_native_resultdb_integration',
-          'pie_fleet',
-          'walleye',
-        ],
-        'test_suites': {
-          'isolated_scripts': 'system_webview_wpt',
-        },
-        'use_swarming': True,
-        'os_type': 'android',
-      },
-      'android-web-platform-pie-x86-fyi-rel': {
+      'android-chrome-pie-x86-wpt-fyi-rel': {
         'mixins': [
           'has_native_resultdb_integration',
           'pie-x86-emulator',
@@ -1181,6 +1169,18 @@
         'use_swarming': True,
         'os_type': 'android',
       },
+      'android-pie-arm64-wpt-rel-non-cq': {
+        'mixins': [
+          'has_native_resultdb_integration',
+          'pie_fleet',
+          'walleye',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'system_webview_wpt',
+        },
+        'use_swarming': True,
+        'os_type': 'android',
+      },
       'android-weblayer-10-x86-rel-tests': {
         'mixins': [
           'has_native_resultdb_integration',
@@ -2538,7 +2538,7 @@
     'name': 'chromium.fyi',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
-      'Comparison Linux': {
+      'Comparison Linux (reclient)': {
         'mixins': [
           'isolate_profile_data',
         ],
@@ -2546,7 +2546,7 @@
           'all'
         ],
       },
-      'Comparison Windows': {
+      'Comparison Windows (reclient)': {
         'mixins': [
           'isolate_profile_data',
         ],
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 5e753e0..677acf00a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -171,28 +171,6 @@
             ]
         }
     ],
-    "AndroidInProductHelpDownloadPage": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Tracking",
-                    "params": {
-                        "availability": ">=14",
-                        "event_1": "name:user_has_seen_dino;comparator:>=1;window:90;storage:360",
-                        "event_trigger": "name:download_page_iph_trigger;comparator:==0;window:90;storage:360",
-                        "event_used": "name:download_page_started;comparator:==0;window:90;storage:360",
-                        "session_rate": "<0"
-                    },
-                    "enable_features": [
-                        "IPH_DownloadPage"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidInProductHelpPwaInstall": [
         {
             "platforms": [
@@ -1525,33 +1503,6 @@
             ]
         }
     ],
-    "ChromeOSKstaled": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "MglruBehavior",
-                    "params": {
-                        "KstaledRatio": "1"
-                    },
-                    "enable_features": [
-                        "KstaledSwap"
-                    ]
-                },
-                {
-                    "name": "KswapdBehavior",
-                    "params": {
-                        "KstaledRatio": "0"
-                    },
-                    "enable_features": [
-                        "KstaledSwap"
-                    ]
-                }
-            ]
-        }
-    ],
     "ChromeOSRawPSIMetrics": [
         {
             "platforms": [
@@ -5875,6 +5826,24 @@
             ]
         }
     ],
+    "TabSwitchMetrics2": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "TabSwitchMetrics2"
+                    ]
+                }
+            ]
+        }
+    ],
     "TabToGTSAnimation": [
         {
             "platforms": [
@@ -5935,24 +5904,6 @@
             ]
         }
     ],
-    "TopCat": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "TopCat1_20190902",
-                    "params": {
-                        "use_topcat_ranker": "true"
-                    },
-                    "enable_features": [
-                        "EnableAppRanker"
-                    ]
-                }
-            ]
-        }
-    ],
     "TreatPreconnectAsDefault": [
         {
             "platforms": [
diff --git a/third_party/androidx/build.gradle.template b/third_party/androidx/build.gradle.template
index 7d4133a..8cc4848 100644
--- a/third_party/androidx/build.gradle.template
+++ b/third_party/androidx/build.gradle.template
@@ -45,6 +45,7 @@
     compile 'androidx.interpolator:interpolator:{{androidx_dependency_version}}'
     compile 'androidx.leanback:leanback:{{androidx_dependency_version}}'
     compile 'androidx.leanback:leanback-preference:{{androidx_dependency_version}}'
+    compile 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
     compile 'androidx.media:media:{{androidx_dependency_version}}'
     compile 'androidx.mediarouter:mediarouter:{{androidx_dependency_version}}'
     compile 'androidx.preference:preference:{{androidx_dependency_version}}'
diff --git a/third_party/blink/common/page/content_to_visible_time_reporter.cc b/third_party/blink/common/page/content_to_visible_time_reporter.cc
index cba388c..b64224e 100644
--- a/third_party/blink/common/page/content_to_visible_time_reporter.cc
+++ b/third_party/blink/common/page/content_to_visible_time_reporter.cc
@@ -60,9 +60,7 @@
 
 }  // namespace
 
-ContentToVisibleTimeReporter::ContentToVisibleTimeReporter()
-    : is_tab_switch_metric2_feature_enabled_(
-          base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2)) {}
+ContentToVisibleTimeReporter::ContentToVisibleTimeReporter() = default;
 
 ContentToVisibleTimeReporter::~ContentToVisibleTimeReporter() = default;
 
@@ -71,14 +69,33 @@
     bool has_saved_frames,
     mojom::RecordContentToVisibleTimeRequestPtr start_state) {
   DCHECK(!start_state->event_start_time.is_null());
+  if (IsTabSwitchMetric2FeatureEnabled() && tab_switch_start_state_ &&
+      tab_switch_start_state_->show_reason_tab_switching &&
+      start_state->show_reason_tab_switching) {
+    // Missed a tab hide, so record an incomplete tab switch. As a side effect
+    // this will reset the state.
+    //
+    // This can happen when the tab is backgrounded, but still visible in a
+    // visible capturer or VR, so the widget is never notified to hide.
+    // TabWasHidden is only called correctly for *hidden* capturers (such as
+    // picture-in-picture). See WebContentsImpl::CalculatePageVisibilityState
+    // for more details.
+    //
+    // TODO(crbug.com/1289266): Refactor visibility states to call TabWasHidden
+    // every time a tab is backgrounded, even if the content is still visible.
+    RecordHistogramsAndTraceEvents(TabSwitchResult::kMissedTabHide,
+                                   true /* show_reason_tab_switching */,
+                                   false /* show_reason_unoccluded */,
+                                   false /* show_reason_bfcache_restore */,
+                                   gfx::PresentationFeedback::Failure());
+  }
   DCHECK(!tab_switch_start_state_);
 
   // Invalidate previously issued callbacks, to avoid accessing a null
   // |tab_switch_start_state_|.
   //
-  // TODO(https://crbug.com/1121339): Make sure that TabWasShown() is never
-  // called twice without a call to TabWasHidden() in-between, and remove this
-  // mitigation.
+  // TODO(crbug.com/1289266): Make sure that TabWasShown() is never called twice
+  // without a call to TabWasHidden() in-between, and remove this mitigation.
   weak_ptr_factory_.InvalidateWeakPtrs();
 
   has_saved_frames_ = has_saved_frames;
@@ -88,7 +105,7 @@
   // once the metrics have been emitted.
   return base::BindOnce(
       &ContentToVisibleTimeReporter::RecordHistogramsAndTraceEvents,
-      weak_ptr_factory_.GetWeakPtr(), false /* is_incomplete */,
+      weak_ptr_factory_.GetWeakPtr(), TabSwitchResult::kSuccess,
       tab_switch_start_state_->show_reason_tab_switching,
       tab_switch_start_state_->show_reason_unoccluded,
       tab_switch_start_state_->show_reason_bfcache_restore);
@@ -110,7 +127,7 @@
 
 void ContentToVisibleTimeReporter::TabWasHidden() {
   if (tab_switch_start_state_) {
-    RecordHistogramsAndTraceEvents(true /* is_incomplete */,
+    RecordHistogramsAndTraceEvents(TabSwitchResult::kIncomplete,
                                    true /* show_reason_tab_switching */,
                                    false /* show_reason_unoccluded */,
                                    false /* show_reason_bfcache_restore */,
@@ -119,8 +136,16 @@
   }
 }
 
+bool ContentToVisibleTimeReporter::IsTabSwitchMetric2FeatureEnabled() {
+  if (!is_tab_switch_metric2_feature_enabled_) {
+    is_tab_switch_metric2_feature_enabled_ =
+        base::FeatureList::IsEnabled(blink::features::kTabSwitchMetrics2);
+  }
+  return *is_tab_switch_metric2_feature_enabled_;
+}
+
 void ContentToVisibleTimeReporter::RecordHistogramsAndTraceEvents(
-    bool is_incomplete,
+    TabSwitchResult tab_switch_result,
     bool show_reason_tab_switching,
     bool show_reason_unoccluded,
     bool show_reason_bfcache_restore,
@@ -130,6 +155,9 @@
   // for recording the event.
   DCHECK(show_reason_bfcache_restore || show_reason_unoccluded ||
          show_reason_tab_switching);
+  // The kPresentationFailure result should only be used if `feedback` has a
+  // failure.
+  DCHECK_NE(tab_switch_result, TabSwitchResult::kPresentationFailure);
 
   if (show_reason_bfcache_restore) {
     RecordBackForwardCacheRestoreMetric(
@@ -144,11 +172,10 @@
     return;
 
   // Tab switching has occurred.
-  auto tab_switch_result = TabSwitchResult::kSuccess;
-  if (is_incomplete)
-    tab_switch_result = TabSwitchResult::kIncomplete;
-  else if (feedback.flags & gfx::PresentationFeedback::kFailure)
+  if (tab_switch_result == TabSwitchResult::kSuccess &&
+      feedback.flags & gfx::PresentationFeedback::kFailure) {
     tab_switch_result = TabSwitchResult::kPresentationFailure;
+  }
 
   const auto tab_switch_duration =
       feedback.timestamp - tab_switch_start_state_->event_start_time;
@@ -168,7 +195,7 @@
   const char* suffix =
       GetHistogramSuffix(has_saved_frames_, *tab_switch_start_state_);
 
-  if (is_tab_switch_metric2_feature_enabled_) {
+  if (IsTabSwitchMetric2FeatureEnabled()) {
     // Record result histogram.
     base::UmaHistogramEnumeration(
         base::StrCat({"Browser.Tabs.TabSwitchResult2.", suffix}),
@@ -176,22 +203,20 @@
 
     // Record latency histogram.
     switch (tab_switch_result) {
-      case TabSwitchResult::kSuccess: {
+      case TabSwitchResult::kSuccess:
         base::UmaHistogramMediumTimes(
             base::StrCat({"Browser.Tabs.TotalSwitchDuration2.", suffix}),
             tab_switch_duration);
         break;
-      }
-      case TabSwitchResult::kIncomplete: {
+      case TabSwitchResult::kMissedTabHide:
+      case TabSwitchResult::kIncomplete:
         base::UmaHistogramMediumTimes(
             base::StrCat(
                 {"Browser.Tabs.TotalIncompleteSwitchDuration2.", suffix}),
             tab_switch_duration);
         break;
-      }
-      case TabSwitchResult::kPresentationFailure: {
+      case TabSwitchResult::kPresentationFailure:
         break;
-      }
     }
   }
 
@@ -211,21 +236,22 @@
 
   // Record latency histogram.
   switch (tab_switch_result) {
-    case TabSwitchResult::kSuccess: {
+    case TabSwitchResult::kSuccess:
       base::UmaHistogramTimes(
           base::StrCat({"Browser.Tabs.TotalSwitchDuration.", suffix}),
           tab_switch_duration);
       break;
-    }
-    case TabSwitchResult::kIncomplete: {
+    case TabSwitchResult::kMissedTabHide:
+      // This was not included in the v1 histograms.
+      DCHECK(IsTabSwitchMetric2FeatureEnabled());
+      [[fallthrough]];
+    case TabSwitchResult::kIncomplete:
       base::UmaHistogramTimes(
           base::StrCat({"Browser.Tabs.TotalIncompleteSwitchDuration.", suffix}),
           tab_switch_duration);
       break;
-    }
-    case TabSwitchResult::kPresentationFailure: {
+    case TabSwitchResult::kPresentationFailure:
       break;
-    }
   }
 
   // Reset tab switch information.
diff --git a/third_party/blink/common/page/content_to_visible_time_reporter_unittest.cc b/third_party/blink/common/page/content_to_visible_time_reporter_unittest.cc
index 31ee215..c0e5561 100644
--- a/third_party/blink/common/page/content_to_visible_time_reporter_unittest.cc
+++ b/third_party/blink/common/page/content_to_visible_time_reporter_unittest.cc
@@ -2,15 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
 #include <strstream>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
+#include "base/containers/extend.h"
 #include "base/rand_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/page/content_to_visible_time_reporter.h"
 #include "ui/gfx/presentation_feedback.h"
 
@@ -28,93 +34,167 @@
 struct TabStateParams {
   bool has_saved_frames;
   bool destination_is_loaded;
-  const char* duration_histogram;
-  const char* incomplete_duration_histogram;
-  const char* result_histogram;
+  bool tab_switch_metrics2_enabled;
+  const char* histogram_suffix;
 };
 
 constexpr TabStateParams kTabStatesToTest[] = {
     // WithSavedFrames
-    {.has_saved_frames = true,
-     .destination_is_loaded = true,
-     .duration_histogram = "Browser.Tabs.TotalSwitchDuration.WithSavedFrames",
-     .incomplete_duration_histogram =
-         "Browser.Tabs.TotalIncompleteSwitchDuration.WithSavedFrames",
-     .result_histogram = "Browser.Tabs.TabSwitchResult.WithSavedFrames"},
+    {
+        .has_saved_frames = true,
+        .destination_is_loaded = true,
+        .tab_switch_metrics2_enabled = false,
+        .histogram_suffix = "WithSavedFrames",
+    },
+    {
+        .has_saved_frames = true,
+        .destination_is_loaded = true,
+        .tab_switch_metrics2_enabled = true,
+        .histogram_suffix = "WithSavedFrames",
+    },
+
     // NoSavedFrames_Loaded
-    {.has_saved_frames = false,
-     .destination_is_loaded = true,
-     .duration_histogram =
-         "Browser.Tabs.TotalSwitchDuration.NoSavedFrames_Loaded",
-     .incomplete_duration_histogram =
-         "Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_Loaded",
-     .result_histogram = "Browser.Tabs.TabSwitchResult.NoSavedFrames_Loaded"},
+    {
+        .has_saved_frames = false,
+        .destination_is_loaded = true,
+        .tab_switch_metrics2_enabled = false,
+        .histogram_suffix = "NoSavedFrames_Loaded",
+    },
+    {
+        .has_saved_frames = false,
+        .destination_is_loaded = true,
+        .tab_switch_metrics2_enabled = true,
+        .histogram_suffix = "NoSavedFrames_Loaded",
+    },
+
     // NoSavedFrames_NotLoaded
-    {.has_saved_frames = false,
-     .destination_is_loaded = false,
-     .duration_histogram =
-         "Browser.Tabs.TotalSwitchDuration.NoSavedFrames_NotLoaded",
-     .incomplete_duration_histogram =
-         "Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_"
-         "NotLoaded",
-     .result_histogram =
-         "Browser.Tabs.TabSwitchResult.NoSavedFrames_NotLoaded"},
+    {
+        .has_saved_frames = false,
+        .destination_is_loaded = false,
+        .tab_switch_metrics2_enabled = false,
+        .histogram_suffix = "NoSavedFrames_NotLoaded",
+    },
+    {
+        .has_saved_frames = false,
+        .destination_is_loaded = false,
+        .tab_switch_metrics2_enabled = true,
+        .histogram_suffix = "NoSavedFrames_NotLoaded",
+    },
 };
 
 class ContentToVisibleTimeReporterTest
     : public ::testing::TestWithParam<TabStateParams> {
  protected:
   ContentToVisibleTimeReporterTest() : tab_state_(GetParam()) {
+    scoped_feature_list_.InitWithFeatureState(
+        blink::features::kTabSwitchMetrics2,
+        tab_state_.tab_switch_metrics2_enabled);
+
+    duration_histograms_.push_back(base::StrCat(
+        {"Browser.Tabs.TotalSwitchDuration.", tab_state_.histogram_suffix}));
+    incomplete_duration_histograms_.push_back(
+        base::StrCat({"Browser.Tabs.TotalIncompleteSwitchDuration.",
+                      tab_state_.histogram_suffix}));
+    result_histograms_.push_back(base::StrCat(
+        {"Browser.Tabs.TabSwitchResult.", tab_state_.histogram_suffix}));
+
+    if (tab_state_.tab_switch_metrics2_enabled) {
+      // Additional metrics logged when the TabSwitchMetrics2 feature is
+      // enabled.
+      // TODO(crbug.com/1164477): When the feature is enabled both the old and
+      // new histograms are logged, so that the old histograms with and without
+      // the feature can be easily compared in an A/B test. When the feature
+      // ships by default remove the old histograms.
+      duration_histograms_.push_back(base::StrCat(
+          {"Browser.Tabs.TotalSwitchDuration2.", tab_state_.histogram_suffix}));
+      incomplete_duration_histograms_.push_back(
+          base::StrCat({"Browser.Tabs.TotalIncompleteSwitchDuration2.",
+                        tab_state_.histogram_suffix}));
+      result_histograms_.push_back(base::StrCat(
+          {"Browser.Tabs.TabSwitchResult2.", tab_state_.histogram_suffix}));
+    }
+
     // Expect all histograms to be empty.
     ExpectHistogramsEmptyExcept({});
   }
 
   void ExpectHistogramsEmptyExcept(
-      std::vector<const char*> histograms_with_values) {
+      const std::vector<std::string>& histograms_with_values) {
     constexpr const char* kAllHistograms[] = {
+        // Pre-TabSwitchMetrics2 feature.
         "Browser.Tabs.TotalSwitchDuration.WithSavedFrames",
         "Browser.Tabs.TotalSwitchDuration.NoSavedFrames_Loaded",
         "Browser.Tabs.TotalSwitchDuration.NoSavedFrames_NotLoaded",
         "Browser.Tabs.TotalIncompleteSwitchDuration.WithSavedFrames",
         "Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_Loaded",
-        "Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_NotLoaded",
+        "Browser.Tabs.TotalIncompleteSwitchDuration.NoSavedFrames_"
+        "NotLoaded",
         "Browser.Tabs.TabSwitchResult.WithSavedFrames",
         "Browser.Tabs.TabSwitchResult.NoSavedFrames_Loaded",
         "Browser.Tabs.TabSwitchResult.NoSavedFrames_NotLoaded",
-        kWebContentsUnOccludedHistogram,
-        kBfcacheRestoreHistogram};
+        // With TabSwitchMetrics2 feature.
+        "Browser.Tabs.TotalSwitchDuration2.WithSavedFrames",
+        "Browser.Tabs.TotalSwitchDuration2.NoSavedFrames_Loaded",
+        "Browser.Tabs.TotalSwitchDuration2.NoSavedFrames_NotLoaded",
+        "Browser.Tabs.TotalIncompleteSwitchDuration2.WithSavedFrames",
+        "Browser.Tabs.TotalIncompleteSwitchDuration2.NoSavedFrames_"
+        "Loaded",
+        "Browser.Tabs.TotalIncompleteSwitchDuration2.NoSavedFrames_"
+        "NotLoaded",
+        "Browser.Tabs.TabSwitchResult2.WithSavedFrames",
+        "Browser.Tabs.TabSwitchResult2.NoSavedFrames_Loaded",
+        "Browser.Tabs.TabSwitchResult2.NoSavedFrames_NotLoaded",
+        // Non-tab switch.
+        kWebContentsUnOccludedHistogram, kBfcacheRestoreHistogram};
+    std::vector<std::string> unexpected_histograms;
     for (const char* histogram : kAllHistograms) {
       if (!base::Contains(histograms_with_values, histogram))
-        ExpectTotalSamples(histogram, 0);
+        unexpected_histograms.push_back(histogram);
+    }
+    ExpectTotalSamples(unexpected_histograms, 0);
+  }
+
+  void ExpectTotalSamples(const std::vector<std::string>& histogram_names,
+                          int expected_count) {
+    for (const std::string& histogram_name : histogram_names) {
+      SCOPED_TRACE(base::StringPrintf("Expect %d samples in %s.",
+                                      expected_count, histogram_name.c_str()));
+      EXPECT_EQ(static_cast<int>(
+                    histogram_tester_.GetAllSamples(histogram_name).size()),
+                expected_count);
     }
   }
 
-  void ExpectTotalSamples(const char* histogram_name, int expected_count) {
-    SCOPED_TRACE(base::StringPrintf("Expect %d samples in %s.", expected_count,
-                                    histogram_name));
-    EXPECT_EQ(static_cast<int>(
-                  histogram_tester_.GetAllSamples(histogram_name).size()),
-              expected_count);
+  void ExpectTimeBucketCounts(const std::vector<std::string>& histogram_names,
+                              base::TimeDelta value,
+                              int count) {
+    for (const std::string& histogram_name : histogram_names) {
+      histogram_tester_.ExpectTimeBucketCount(histogram_name, value, count);
+    }
   }
 
-  void ExpectTimeBucketCount(const char* histogram_name,
-                             base::TimeDelta value,
-                             int count) {
-    histogram_tester_.ExpectTimeBucketCount(histogram_name, value, count);
-  }
-
-  void ExpectResultBucketCount(
-      const char* histogram_name,
+  void ExpectResultBucketCounts(
+      const std::vector<std::string>& histogram_names,
       ContentToVisibleTimeReporter::TabSwitchResult value,
       int count) {
-    histogram_tester_.ExpectBucketCount(histogram_name, value, count);
+    for (const std::string& histogram_name : histogram_names) {
+      histogram_tester_.ExpectBucketCount(histogram_name, value, count);
+    }
   }
 
+  // Create `feature_list_` before `task_environment_` and destroy it after to
+  // avoid a race in destruction.
+  base::test::ScopedFeatureList scoped_feature_list_;
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   ContentToVisibleTimeReporter tab_switch_time_recorder_;
   base::HistogramTester histogram_tester_;
   TabStateParams tab_state_;
+
+  // Expected histogram names to be logged for the given TabStateParams.
+  std::vector<std::string> duration_histograms_;
+  std::vector<std::string> incomplete_duration_histograms_;
+  std::vector<std::string> result_histograms_;
 };
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -137,17 +217,19 @@
       end, end - start, gfx::PresentationFeedback::Flags::kHWCompletion);
   std::move(callback).Run(presentation_feedback);
 
-  ExpectHistogramsEmptyExcept(
-      {tab_state_.duration_histogram, tab_state_.result_histogram});
+  std::vector<std::string> expected_histograms;
+  base::Extend(expected_histograms, duration_histograms_);
+  base::Extend(expected_histograms, result_histograms_);
+  ExpectHistogramsEmptyExcept(expected_histograms);
 
   // Duration.
-  ExpectTotalSamples(tab_state_.duration_histogram, 1);
-  ExpectTimeBucketCount(tab_state_.duration_histogram, kDuration, 1);
+  ExpectTotalSamples(duration_histograms_, 1);
+  ExpectTimeBucketCounts(duration_histograms_, kDuration, 1);
 
   // Result.
-  ExpectTotalSamples(tab_state_.result_histogram, 1);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectTotalSamples(result_histograms_, 1);
+  ExpectResultBucketCounts(
+      result_histograms_,
       ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
 }
 
@@ -164,12 +246,12 @@
           /* show_reason_bfcache_restore */ false));
   std::move(callback).Run(gfx::PresentationFeedback::Failure());
 
-  ExpectHistogramsEmptyExcept({tab_state_.result_histogram});
+  ExpectHistogramsEmptyExcept(result_histograms_);
 
   // Result (no duration is recorded on presentation failure).
-  ExpectTotalSamples(tab_state_.result_histogram, 1);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectTotalSamples(result_histograms_, 1);
+  ExpectResultBucketCounts(
+      result_histograms_,
       ContentToVisibleTimeReporter::TabSwitchResult::kPresentationFailure, 1);
 }
 
@@ -188,17 +270,19 @@
   task_environment_.FastForwardBy(kDuration);
   tab_switch_time_recorder_.TabWasHidden();
 
-  ExpectHistogramsEmptyExcept(
-      {tab_state_.result_histogram, tab_state_.incomplete_duration_histogram});
+  std::vector<std::string> expected_histograms;
+  base::Extend(expected_histograms, result_histograms_);
+  base::Extend(expected_histograms, incomplete_duration_histograms_);
+  ExpectHistogramsEmptyExcept(expected_histograms);
 
   // Duration.
-  ExpectTotalSamples(tab_state_.incomplete_duration_histogram, 1);
-  ExpectTimeBucketCount(tab_state_.incomplete_duration_histogram, kDuration, 1);
+  ExpectTotalSamples(incomplete_duration_histograms_, 1);
+  ExpectTimeBucketCounts(incomplete_duration_histograms_, kDuration, 1);
 
   // Result.
-  ExpectTotalSamples(tab_state_.result_histogram, 1);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectTotalSamples(result_histograms_, 1);
+  ExpectResultBucketCounts(
+      result_histograms_,
       ContentToVisibleTimeReporter::TabSwitchResult::kIncomplete, 1);
 
   const auto start2 = base::TimeTicks::Now();
@@ -214,23 +298,81 @@
       end2, end2 - start2, gfx::PresentationFeedback::Flags::kHWCompletion);
   std::move(callback2).Run(presentation_feedback);
 
-  ExpectHistogramsEmptyExcept({tab_state_.duration_histogram,
-                               tab_state_.result_histogram,
-                               tab_state_.incomplete_duration_histogram});
+  // Now the tab switch completes, and adds a duration histogram.
+  base::Extend(expected_histograms, duration_histograms_);
+  ExpectHistogramsEmptyExcept(expected_histograms);
 
   // Duration.
-  ExpectTotalSamples(tab_state_.incomplete_duration_histogram, 1);
-  ExpectTimeBucketCount(tab_state_.incomplete_duration_histogram, kDuration, 1);
-  ExpectTotalSamples(tab_state_.duration_histogram, 1);
-  ExpectTimeBucketCount(tab_state_.duration_histogram, kOtherDuration, 1);
+  ExpectTotalSamples(incomplete_duration_histograms_, 1);
+  ExpectTimeBucketCounts(incomplete_duration_histograms_, kDuration, 1);
+  ExpectTotalSamples(duration_histograms_, 1);
+  ExpectTimeBucketCounts(duration_histograms_, kOtherDuration, 1);
 
   // Result.
-  ExpectTotalSamples(tab_state_.result_histogram, 2);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectTotalSamples(result_histograms_, 2);
+  ExpectResultBucketCounts(
+      result_histograms_,
       ContentToVisibleTimeReporter::TabSwitchResult::kIncomplete, 1);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectResultBucketCounts(
+      result_histograms_,
+      ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
+}
+
+// When the TabSwitchMetrics2 feature is enabled, if TabWasHidden is not called
+// an incomplete tab switch is reported.
+// TODO(crbug.com/1289266): Find and remove all cases where TabWasHidden is not
+// called.
+TEST_P(ContentToVisibleTimeReporterTest, MissingTabWasHidden) {
+  if (!tab_state_.tab_switch_metrics2_enabled)
+    GTEST_SKIP();
+
+  const auto start1 = base::TimeTicks::Now();
+  auto callback1 = tab_switch_time_recorder_.TabWasShown(
+      tab_state_.has_saved_frames,
+      blink::mojom::RecordContentToVisibleTimeRequest::New(
+          start1, tab_state_.destination_is_loaded,
+          /* show_reason_tab_switching */ true,
+          /* show_reason_unoccluded */ false,
+          /* show_reason_bfcache_restore */ false));
+
+  task_environment_.FastForwardBy(kDuration);
+
+  ExpectHistogramsEmptyExcept({});
+
+  const auto start2 = base::TimeTicks::Now();
+  auto callback2 = tab_switch_time_recorder_.TabWasShown(
+      tab_state_.has_saved_frames,
+      blink::mojom::RecordContentToVisibleTimeRequest::New(
+          start2, tab_state_.destination_is_loaded,
+          /* show_reason_tab_switching */ true,
+          /* show_reason_unoccluded */ false,
+          /* show_reason_bfcache_restore */ false));
+  const auto end2 = start2 + kOtherDuration;
+  auto presentation_feedback = gfx::PresentationFeedback(
+      end2, end2 - start2, gfx::PresentationFeedback::Flags::kHWCompletion);
+  std::move(callback2).Run(presentation_feedback);
+
+  // IncompleteDuration should be logged for the first TabWasShown, and Duration
+  // for the second.
+  std::vector<std::string> expected_histograms;
+  base::Extend(expected_histograms, duration_histograms_);
+  base::Extend(expected_histograms, result_histograms_);
+  base::Extend(expected_histograms, incomplete_duration_histograms_);
+  ExpectHistogramsEmptyExcept(expected_histograms);
+
+  // Duration.
+  ExpectTotalSamples({incomplete_duration_histograms_}, 1);
+  ExpectTimeBucketCounts({incomplete_duration_histograms_}, kDuration, 1);
+  ExpectTotalSamples({duration_histograms_}, 1);
+  ExpectTimeBucketCounts({duration_histograms_}, kOtherDuration, 1);
+
+  // Result.
+  ExpectTotalSamples({result_histograms_}, 2);
+  ExpectResultBucketCounts(
+      {result_histograms_},
+      ContentToVisibleTimeReporter::TabSwitchResult::kMissedTabHide, 1);
+  ExpectResultBucketCounts(
+      {result_histograms_},
       ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
 }
 
@@ -252,8 +394,8 @@
   ExpectHistogramsEmptyExcept({kWebContentsUnOccludedHistogram});
 
   // UnOccluded.
-  ExpectTotalSamples(kWebContentsUnOccludedHistogram, 1);
-  ExpectTimeBucketCount(kWebContentsUnOccludedHistogram, kDuration, 1);
+  ExpectTotalSamples({kWebContentsUnOccludedHistogram}, 1);
+  ExpectTimeBucketCounts({kWebContentsUnOccludedHistogram}, kDuration, 1);
 }
 
 // Time is properly recorded to histogram when we have unoccluded event
@@ -273,23 +415,24 @@
       end, end - start, gfx::PresentationFeedback::Flags::kHWCompletion);
   std::move(callback).Run(presentation_feedback);
 
-  ExpectHistogramsEmptyExcept({tab_state_.duration_histogram,
-                               tab_state_.result_histogram,
-                               kWebContentsUnOccludedHistogram});
+  std::vector<std::string> expected_histograms{kWebContentsUnOccludedHistogram};
+  base::Extend(expected_histograms, duration_histograms_);
+  base::Extend(expected_histograms, result_histograms_);
+  ExpectHistogramsEmptyExcept(expected_histograms);
 
   // Duration.
-  ExpectTotalSamples(tab_state_.duration_histogram, 1);
-  ExpectTimeBucketCount(tab_state_.duration_histogram, kDuration, 1);
+  ExpectTotalSamples(duration_histograms_, 1);
+  ExpectTimeBucketCounts(duration_histograms_, kDuration, 1);
 
   // Result.
-  ExpectTotalSamples(tab_state_.result_histogram, 1);
-  ExpectResultBucketCount(
-      tab_state_.result_histogram,
+  ExpectTotalSamples(result_histograms_, 1);
+  ExpectResultBucketCounts(
+      result_histograms_,
       ContentToVisibleTimeReporter::TabSwitchResult::kSuccess, 1);
 
   // UnOccluded.
-  ExpectTotalSamples(kWebContentsUnOccludedHistogram, 1);
-  ExpectTimeBucketCount(kWebContentsUnOccludedHistogram, kDuration, 1);
+  ExpectTotalSamples({kWebContentsUnOccludedHistogram}, 1);
+  ExpectTimeBucketCounts({kWebContentsUnOccludedHistogram}, kDuration, 1);
 }
 
 // Time is properly recorded to histogram when we have bfcache restore event.
@@ -310,8 +453,8 @@
   ExpectHistogramsEmptyExcept({kBfcacheRestoreHistogram});
 
   // Bfcache restore.
-  ExpectTotalSamples(kBfcacheRestoreHistogram, 1);
-  ExpectTimeBucketCount(kBfcacheRestoreHistogram, kDuration, 1);
+  ExpectTotalSamples({kBfcacheRestoreHistogram}, 1);
+  ExpectTimeBucketCounts({kBfcacheRestoreHistogram}, kDuration, 1);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/public/common/page/content_to_visible_time_reporter.h b/third_party/blink/public/common/page/content_to_visible_time_reporter.h
index 38f6998..569d4811 100644
--- a/third_party/blink/public/common/page/content_to_visible_time_reporter.h
+++ b/third_party/blink/public/common/page/content_to_visible_time_reporter.h
@@ -8,6 +8,7 @@
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/mojom/widget/record_content_to_visible_time_request.mojom.h"
 
@@ -30,7 +31,10 @@
     kIncomplete = 1,
     // Compositor reported a failure after a tab switch.
     kPresentationFailure = 2,
-    kMaxValue = kPresentationFailure,
+    // TabWasShown called twice for a frame without TabWasHidden between. Treat
+    // the first TabWasShown as an incomplete tab switch.
+    kMissedTabHide = 3,
+    kMaxValue = kMissedTabHide,
   };
 
   ContentToVisibleTimeReporter();
@@ -58,9 +62,13 @@
   void TabWasHidden();
 
  private:
-  // Records histograms and trace events for the current tab switch.
+  bool IsTabSwitchMetric2FeatureEnabled();
+
+  // Records histograms and trace events for the current tab switch. If
+  // `tab_switch_result` is kSuccess but `feedback` contains a failure flag, the
+  // result will be overridden with kPresentationFailure.
   void RecordHistogramsAndTraceEvents(
-      bool is_incomplete,
+      TabSwitchResult tab_switch_result,
       bool show_reason_tab_switching,
       bool show_reason_unoccluded,
       bool show_reason_bfcache_restore,
@@ -74,7 +82,7 @@
   mojom::RecordContentToVisibleTimeRequestPtr tab_switch_start_state_;
 
   // Cache the feature value for faster lookups.
-  bool is_tab_switch_metric2_feature_enabled_;
+  absl::optional<bool> is_tab_switch_metric2_feature_enabled_;
 
   base::WeakPtrFactory<ContentToVisibleTimeReporter> weak_ptr_factory_{this};
 };
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 13a7e61..b5345d9 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -773,6 +773,36 @@
       # are respected. Any injected via javascript (or other means) are ignored.
       MetaTagModifiedHTML
 
+  type FederatedAuthRequestIssueDetails extends object
+    properties
+      FederatedAuthRequestIssueReason federatedAuthRequestIssueReason
+
+  # Represents the failure reason when a federated authentication reason fails.
+  # Should be updated alongside RequestIdTokenStatus in
+  # third_party/blink/public/mojom/webid/federated_auth_request.mojom to include
+  # all cases except for success.
+  type FederatedAuthRequestIssueReason extends string
+    enum
+      ApprovalDeclined
+      TooManyRequests
+      WellKnownHttpNotFound
+      WellKnownNoResponse
+      WellKnownInvalidResponse
+      ClientIdMetadataHttpNotFound
+      ClientIdMetadataNoResponse
+      ClientIdMetadataInvalidResponse
+      ErrorFetchingSignin
+      InvalidSigninResponse
+      AccountsHttpNotFound
+      AccountsNoResponse
+      AccountsInvalidResponse
+      IdTokenHttpNotFound
+      IdTokenNoResponse
+      IdTokenInvalidResponse
+      IdTokenInvalidRequest
+      ErrorIdToken
+      Canceled
+
   # This issue tracks client hints related issues. It's used to deprecate old
   # features, encourage the use of new ones, and provide general guidance.
   type ClientHintIssueDetails extends object
@@ -800,6 +830,7 @@
       GenericIssue
       DeprecationIssue
       ClientHintIssue
+      FederatedAuthRequestIssue
 
   # This struct holds a list of optional fields with additional information
   # specific to the kind of issue. When adding a new issue code, please also
@@ -821,6 +852,7 @@
       optional GenericIssueDetails genericIssueDetails
       optional DeprecationIssueDetails deprecationIssueDetails
       optional ClientHintIssueDetails clientHintIssueDetails
+      optional FederatedAuthRequestIssueDetails federatedAuthRequestIssueDetails
 
   # A unique id for a DevTools inspector issue. Allows other entities (e.g.
   # exceptions, CDP message, console messages, etc.) to reference an issue.
diff --git a/third_party/blink/public/mojom/devtools/inspector_issue.mojom b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
index d0231f50..34b623d 100644
--- a/third_party/blink/public/mojom/devtools/inspector_issue.mojom
+++ b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
@@ -8,6 +8,7 @@
 import "services/network/public/mojom/cookie_manager.mojom";
 import "services/network/public/mojom/blocked_by_response_reason.mojom";
 import "third_party/blink/public/mojom/fetch/fetch_api_request.mojom";
+import "third_party/blink/public/mojom/webid/federated_auth_request.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
 
 // A code that uniquely identifies an issue. This type should be descriptive
@@ -21,6 +22,7 @@
   kTrustedWebActivityIssue,
   kHeavyAdIssue,
   kLowTextContrastIssue,
+  kFederatedAuthRequestIssue,
   kGenericIssue,
 };
 
@@ -175,6 +177,10 @@
   string font_weight;
 };
 
+struct FederatedAuthRequestIssueDetails {
+  RequestIdTokenStatus status;
+};
+
 enum GenericIssueErrorType {
   kCrossOriginPortalPostMessageError,
 };
@@ -195,6 +201,7 @@
   TrustedWebActivityIssueDetails? twa_issue_details;
   HeavyAdIssueDetails? heavy_ad_issue_details;
   LowTextContrastIssue? low_text_contrast_details;
+  FederatedAuthRequestIssueDetails? federated_auth_request_details;
   GenericIssueDetails? generic_issue_details;
   mojo_base.mojom.UnguessableToken? issue_id;
 };
diff --git a/third_party/blink/public/mojom/webid/federated_auth_request.mojom b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
index d5d345b..3c1303a 100644
--- a/third_party/blink/public/mojom/webid/federated_auth_request.mojom
+++ b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
@@ -10,6 +10,10 @@
 //
 // Proposal: https://github.com/WICG/WebID
 
+// Represents the fetch result from a federated authentication request. Should
+// be updated alongside FederatedAuthRequestIssueReason in
+// third_party/blink/public/devtools_protocol/browser_protocol.pdl, which
+// includes all cases except for kSuccess.
 enum RequestIdTokenStatus {
   kSuccess,
   kApprovalDeclined,
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h b/third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h
index c165309..d9a7195 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h
@@ -75,8 +75,10 @@
   kReadableStreamTransferTag = 'r',   // index:uint32_t
   kTransformStreamTransferTag = 'm',  // index:uint32_t
   kWritableStreamTransferTag = 'w',   // index:uint32_t
-  kDOMPointTag = 'Q',                 // x:Double, y:Double, z:Double, w:Double
-  kDOMPointReadOnlyTag = 'W',         // x:Double, y:Double, z:Double, w:Double
+  // TODO(crbug.com/1288839): document format as soon as it is determined.
+  kMediaStreamTrack = 's',
+  kDOMPointTag = 'Q',          // x:Double, y:Double, z:Double, w:Double
+  kDOMPointReadOnlyTag = 'W',  // x:Double, y:Double, z:Double, w:Double
   kDOMRectTag = 'E',          // x:Double, y:Double, width:Double, height:Double
   kDOMRectReadOnlyTag = 'R',  // x:Double, y:Double, width:Double, height:Double
   kDOMQuadTag = 'T',          // p1:Double, p2:Double, p3:Double, p4:Double
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/transferables.h b/third_party/blink/renderer/bindings/core/v8/serialization/transferables.h
index c0fea6a..24a4546d 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/transferables.h
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/transferables.h
@@ -21,6 +21,7 @@
 class ReadableStream;
 class WritableStream;
 class TransformStream;
+class MediaStreamTrack;
 
 using ArrayBufferArray = HeapVector<Member<DOMArrayBufferBase>>;
 using ImageBitmapArray = HeapVector<Member<ImageBitmap>>;
@@ -30,6 +31,7 @@
 using ReadableStreamArray = HeapVector<Member<ReadableStream>>;
 using WritableStreamArray = HeapVector<Member<WritableStream>>;
 using TransformStreamArray = HeapVector<Member<TransformStream>>;
+using MediaStreamTrackArray = HeapVector<Member<MediaStreamTrack>>;
 
 class CORE_EXPORT Transferables final {
   STACK_ALLOCATED();
@@ -50,6 +52,7 @@
   ReadableStreamArray readable_streams;
   WritableStreamArray writable_streams;
   TransformStreamArray transform_streams;
+  MediaStreamTrackArray media_stream_tracks;
 
   class CORE_EXPORT TransferList : public GarbageCollectedMixin {
    public:
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index 29b2887..3484f0d 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -379,8 +379,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_validity_state_flags.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_wheel_event_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_wheel_event_init.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay_geometry_change_event_init.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay_geometry_change_event_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_post_message_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_post_message_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_worker_options.cc",
@@ -1473,10 +1471,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_wheel_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay_geometry_change_event.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay_geometry_change_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_worker.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_worker.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_worker_global_scope.cc",
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 3d430a3..0f2ce99 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -2655,6 +2655,26 @@
   ]
 }
 
+# WindowControlsOverlay
+# This uses target_os rather than current_os (which is what is_android is set
+# from) for the case of generating the v8 context snapshot for android. When
+# generating the snapshot for android, blink is compiled with
+# current_os="linux" and target_os="android". Using target_os is necessary as
+# we need to compile in the same way as would happen when current_os="android".
+if (target_os != "android") {
+  generated_dictionary_sources_in_modules += [
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay_geometry_change_event_init.cc",
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay_geometry_change_event_init.h",
+  ]
+
+  generated_enumeration_sources_in_modules += [
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay.cc",
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay.h",
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay_geometry_change_event.cc",
+    "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay_geometry_change_event.h",
+  ]
+}
+
 generated_interface_sources_in_modules += [
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/init_idl_interfaces.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/init_idl_interfaces.h",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index 48afc56e..11e30d7 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -302,7 +302,6 @@
           "//third_party/blink/renderer/core/frame/navigator_ua_brand_version.idl",
           "//third_party/blink/renderer/core/frame/navigator_ua_data.idl",
           "//third_party/blink/renderer/core/frame/navigator_user_activation.idl",
-          "//third_party/blink/renderer/core/frame/navigator_window_controls_overlay.idl",
           "//third_party/blink/renderer/core/frame/permissions_policy_violation_report_body.idl",
           "//third_party/blink/renderer/core/frame/report.idl",
           "//third_party/blink/renderer/core/frame/report_body.idl",
@@ -320,9 +319,6 @@
           "//third_party/blink/renderer/core/frame/visual_viewport.idl",
           "//third_party/blink/renderer/core/frame/window.idl",
           "//third_party/blink/renderer/core/frame/window_attribution_reporting.idl",
-          "//third_party/blink/renderer/core/frame/window_controls_overlay.idl",
-          "//third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.idl",
-          "//third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event_init.idl",
           "//third_party/blink/renderer/core/frame/window_event_handlers.idl",
           "//third_party/blink/renderer/core/frame/window_or_worker_global_scope.idl",
           "//third_party/blink/renderer/core/frame/window_post_message_options.idl",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index a0b21ce..91f11ff 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -1185,6 +1185,23 @@
           "abspath")
 }
 
+# WindowControlsOverlay
+# This uses target_os rather than current_os (which is what is_android is set
+# from) for the case of generating the v8 context snapshot for android. When
+# generating the snapshot for android, blink is compiled with
+# current_os="linux" and target_os="android". Using target_os is necessary as
+# we need to compile in the same way as would happen when current_os="android".
+if (target_os != "android") {
+  static_idl_files_in_modules += get_path_info(
+          [
+            "//third_party/blink/renderer/modules/window_controls_overlay/navigator_window_controls_overlay.idl",
+            "//third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.idl",
+            "//third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.idl",
+            "//third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event_init.idl",
+          ],
+          "abspath")
+}
+
 # Statically-defined (not runtime-generated) IDL files in 'modules' component.
 # These IDL definitions are used only for testing.
 static_idl_files_in_modules_for_testing = get_path_info(
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.cc b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.cc
index 53a691e..cf11e8d 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/modules/file_system_access/file_system_directory_handle.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_file_handle.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_system.h"
+#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_certificate.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_certificate_generator.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
@@ -89,6 +90,8 @@
       return ReadEncodedAudioChunk();
     case kEncodedVideoChunkTag:
       return ReadEncodedVideoChunk();
+    case kMediaStreamTrack:
+      return ReadMediaStreamTrack();
     default:
       break;
   }
@@ -522,4 +525,13 @@
   return MakeGarbageCollected<EncodedVideoChunk>(buffers[index]);
 }
 
+MediaStreamTrack* V8ScriptValueDeserializerForModules::ReadMediaStreamTrack() {
+  if (!RuntimeEnabledFeatures::MediaStreamTrackTransferEnabled(
+          ExecutionContext::From(GetScriptState()))) {
+    return nullptr;
+  }
+
+  return MediaStreamTrack::Create(ExecutionContext::From(GetScriptState()));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.h b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.h
index 2e2e0776e..af56602e 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.h
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_deserializer_for_modules.h
@@ -56,6 +56,7 @@
   VideoFrame* ReadVideoFrame();
   EncodedAudioChunk* ReadEncodedAudioChunk();
   EncodedVideoChunk* ReadEncodedVideoChunk();
+  MediaStreamTrack* ReadMediaStreamTrack();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
index 510b6984..8a0527a 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/public/platform/web_crypto_key.h"
 #include "third_party/blink/public/platform/web_crypto_key_algorithm.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_rect_read_only.h"
 #include "third_party/blink/renderer/bindings/modules/v8/serialization/web_crypto_sub_tags.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_data.h"
@@ -19,6 +20,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_file_system_directory_handle.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_file_system_file_handle.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_landmark.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_point_2d.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_certificate.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
@@ -28,6 +30,7 @@
 #include "third_party/blink/renderer/modules/crypto/crypto_key.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_handle.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_system.h"
+#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_certificate.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame_delegate.h"
@@ -76,6 +79,23 @@
     transfer_list->video_frames.push_back(video_frame);
     return true;
   }
+
+  if (V8MediaStreamTrack::HasInstance(object, isolate) &&
+      RuntimeEnabledFeatures::MediaStreamTrackTransferEnabled(
+          CurrentExecutionContext(isolate))) {
+    MediaStreamTrack* track =
+        V8MediaStreamTrack::ToImpl(v8::Local<v8::Object>::Cast(object));
+    if (transferables.media_stream_tracks.Contains(track)) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataCloneError,
+          "MediaStreamTrack at index " + String::Number(object_index) +
+              " is a duplicate of an earlier MediaStreamTrack.");
+      return false;
+    }
+    transferables.media_stream_tracks.push_back(track);
+    return true;
+  }
+
   return false;
 }
 
@@ -204,6 +224,18 @@
     auto data = wrappable->ToImpl<EncodedVideoChunk>()->buffer();
     return WriteDecoderBuffer(std::move(data), /*for_audio=*/false);
   }
+  if (wrapper_type_info == V8MediaStreamTrack::GetWrapperTypeInfo() &&
+      RuntimeEnabledFeatures::MediaStreamTrackTransferEnabled(
+          ExecutionContext::From(GetScriptState()))) {
+    if (IsForStorage()) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataCloneError,
+          "A MediaStreamTrack cannot be serialized for storage.");
+      return false;
+    }
+
+    return WriteMediaStreamTrack();
+  }
   return false;
 }
 
@@ -473,4 +505,10 @@
   return true;
 }
 
+bool V8ScriptValueSerializerForModules::WriteMediaStreamTrack() {
+  WriteTag(kMediaStreamTrack);
+
+  return true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.h b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.h
index b0566a3..63a59b8b 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.h
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.h
@@ -53,6 +53,7 @@
   bool WriteMediaAudioBuffer(scoped_refptr<media::AudioBuffer>);
   bool WriteDecoderBuffer(scoped_refptr<media::DecoderBuffer> data,
                           bool for_audio);
+  bool WriteMediaStreamTrack();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index 18bfff86..6ed84d9 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -593,7 +593,6 @@
     int compositor_group,
     const PaintArtifactCompositor* paint_artifact_compositor,
     bool start_on_compositor) {
-
   bool soft_change =
       compositor_state_ &&
       (Paused() || compositor_state_->playback_rate != EffectivePlaybackRate());
@@ -637,6 +636,12 @@
       RecordCompositorAnimationFailureReasons(failure_reasons);
 
       if (failure_reasons == CompositorAnimations::kNoFailure) {
+        // We could still have a stale compositor keyframe model ID if
+        // a previous cancel failed due to not having a layout object at the
+        // time of the cancel operation. The start and stop of an animation
+        // for a marquee element does not depend on having a layout object.
+        if (HasActiveAnimationsOnCompositor())
+          CancelAnimationOnCompositor();
         CreateCompositorAnimation();
         StartAnimationOnCompositor(paint_artifact_compositor);
         compositor_state_ = std::make_unique<CompositorState>(*this);
@@ -2320,10 +2325,6 @@
 
   if (reason == kTimingUpdateForAnimationFrame) {
     if (idle || CalculateAnimationPlayState() == kFinished) {
-      // TODO(crbug.com/1029348): Per spec, we should have a microtask
-      // checkpoint right after the update cycle. Once this is fixed we should
-      // no longer need to force a synchronous resolution here.
-      AsyncFinishMicrotask();
       finished_ = true;
     }
   }
@@ -2659,10 +2660,8 @@
   // 1. The existence of the animation is not prescribed by markup. That is, it
   //    is not a CSS animation with an owning element, nor a CSS transition with
   //    an owning element.
-  if (IsCSSAnimation() || IsCSSTransition()) {
-    // TODO(crbug.com/981905): Add OwningElement method to Animation and
-    // override in CssAnimations and CssTransitions. Only bail here if the
-    // animation has an owning element.
+  if ((IsCSSAnimation() || IsCSSTransition()) && OwningElement()) {
+    // A CSS animation or transition that is bound to markup is not replaceable.
     return false;
   }
 
diff --git a/third_party/blink/renderer/core/animation/document_animations.cc b/third_party/blink/renderer/core/animation/document_animations.cc
index 91c12b1..9e128f81 100644
--- a/third_party/blink/renderer/core/animation/document_animations.cc
+++ b/third_party/blink/renderer/core/animation/document_animations.cc
@@ -52,6 +52,7 @@
 #include "third_party/blink/renderer/core/page/page_animator.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/bindings/microtask.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 
 namespace blink {
 
@@ -280,10 +281,13 @@
 
   // The list of animations for removal is constructed in reverse composite
   // ordering for efficiency. Flip the ordering to ensure that events are
-  // dispatched in composite order.
+  // dispatched in composite order.  Queue as a microtask so that the finished
+  // event is dispatched ahead of the remove event.
   for (auto it = animations_to_remove.rbegin();
        it != animations_to_remove.rend(); it++) {
-    (*it)->RemoveReplacedAnimation();
+    Animation* animation = *it;
+    Microtask::EnqueueMicrotask(WTF::Bind(&Animation::RemoveReplacedAnimation,
+                                          WrapWeakPersistent(animation)));
   }
 }
 
diff --git a/third_party/blink/renderer/core/animation/interpolable_length.cc b/third_party/blink/renderer/core/animation/interpolable_length.cc
index aa41b2c0..7813104 100644
--- a/third_party/blink/renderer/core/animation/interpolable_length.cc
+++ b/third_party/blink/renderer/core/animation/interpolable_length.cc
@@ -166,7 +166,7 @@
 
   DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, zero_percent,
                       {PercentageNode(0)});
-  SetExpression(*CSSMathExpressionBinaryOperation::Create(
+  SetExpression(*CSSMathExpressionOperation::CreateArithmeticOperation(
       expression_, zero_percent, CSSMathOperator::kAdd));
 }
 
@@ -181,8 +181,9 @@
 
   DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, hundred_percent,
                       {PercentageNode(100)});
-  SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
-      hundred_percent, expression_, CSSMathOperator::kSubtract));
+  SetExpression(
+      *CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
+          hundred_percent, expression_, CSSMathOperator::kSubtract));
 }
 
 static double ClampToRange(double x, Length::ValueRange range) {
@@ -301,7 +302,7 @@
     if (!root_node) {
       root_node = current_node;
     } else {
-      root_node = CSSMathExpressionBinaryOperation::Create(
+      root_node = CSSMathExpressionOperation::CreateArithmeticOperation(
           root_node, current_node, CSSMathOperator::kAdd);
     }
   }
@@ -320,8 +321,9 @@
   }
 
   DCHECK(IsExpression());
-  SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
-      expression_, NumberNode(scale), CSSMathOperator::kMultiply));
+  SetExpression(
+      *CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
+          expression_, NumberNode(scale), CSSMathOperator::kMultiply));
 }
 
 void InterpolableLength::Add(const InterpolableValue& other) {
@@ -336,7 +338,7 @@
   }
 
   CSSMathExpressionNode* result =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           &AsExpression(), &other_length.AsExpression(), CSSMathOperator::kAdd);
   SetExpression(*result);
 }
@@ -354,10 +356,10 @@
   }
 
   CSSMathExpressionNode* scaled =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           &AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply);
   CSSMathExpressionNode* result =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           scaled, &other_length.AsExpression(), CSSMathOperator::kAdd);
   SetExpression(*result);
 }
@@ -394,15 +396,15 @@
   }
 
   CSSMathExpressionNode* blended_from =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           &AsExpression(), NumberNode(1 - progress),
           CSSMathOperator::kMultiply);
   CSSMathExpressionNode* blended_to =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           &to_length.AsExpression(), NumberNode(progress),
           CSSMathOperator::kMultiply);
   CSSMathExpressionNode* result_expression =
-      CSSMathExpressionBinaryOperation::CreateSimplified(
+      CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           blended_from, blended_to, CSSMathOperator::kAdd);
   result_length.SetExpression(*result_expression);
 
diff --git a/third_party/blink/renderer/core/animation/keyframe.h b/third_party/blink/renderer/core/animation/keyframe.h
index 8c0e8a2..d5b943f4 100644
--- a/third_party/blink/renderer/core/animation/keyframe.h
+++ b/third_party/blink/renderer/core/animation/keyframe.h
@@ -7,10 +7,10 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/renderer/core/animation/animation_effect.h"
 #include "third_party/blink/renderer/core/animation/effect_model.h"
 #include "third_party/blink/renderer/core/animation/property_handle.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/animation/timing_function.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index d7e7de0a..b47bb47 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -396,11 +396,8 @@
 
 // ------ Start of CSSMathExpressionOperation member functions ------
 
-// TODO(pjh0718): Integrate CSSMathExpressionBinary/VariadicOperation to
-// CSSMathExpressionOperation.
-
 // static
-CSSMathExpressionNode* CSSMathExpressionBinaryOperation::Create(
+CSSMathExpressionNode* CSSMathExpressionOperation::CreateArithmeticOperation(
     const CSSMathExpressionNode* left_side,
     const CSSMathExpressionNode* right_side,
     CSSMathOperator op) {
@@ -412,12 +409,12 @@
   if (new_category == kCalcOther)
     return nullptr;
 
-  return MakeGarbageCollected<CSSMathExpressionBinaryOperation>(
-      left_side, right_side, op, new_category);
+  return MakeGarbageCollected<CSSMathExpressionOperation>(left_side, right_side,
+                                                          op, new_category);
 }
 
 // static
-CSSMathExpressionVariadicOperation* CSSMathExpressionVariadicOperation::Create(
+CSSMathExpressionNode* CSSMathExpressionOperation::CreateComparisonFunction(
     Operands&& operands,
     CSSMathOperator op) {
   DCHECK(op == CSSMathOperator::kMin || op == CSSMathOperator::kMax);
@@ -434,17 +431,18 @@
     if (category == kCalcOther)
       return nullptr;
   }
-  return MakeGarbageCollected<CSSMathExpressionVariadicOperation>(
+  return MakeGarbageCollected<CSSMathExpressionOperation>(
       category, std::move(operands), op);
 }
 
 // static
-CSSMathExpressionNode* CSSMathExpressionBinaryOperation::CreateSimplified(
+CSSMathExpressionNode*
+CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
     const CSSMathExpressionNode* left_side,
     const CSSMathExpressionNode* right_side,
     CSSMathOperator op) {
   if (left_side->IsMathFunction() || right_side->IsMathFunction())
-    return Create(left_side, right_side, op);
+    return CreateArithmeticOperation(left_side, right_side, op);
 
   CalculationCategory left_category = left_side->Category();
   CalculationCategory right_category = right_side->Category();
@@ -454,7 +452,7 @@
   // Simplify numbers.
   if (left_category == kCalcNumber && right_category == kCalcNumber) {
     return CSSMathExpressionNumericLiteral::Create(
-        EvaluateOperator(left_side->DoubleValue(), right_side->DoubleValue(),
+        EvaluateOperator({left_side->DoubleValue(), right_side->DoubleValue()},
                          op),
         CSSPrimitiveValue::UnitType::kNumber);
   }
@@ -467,8 +465,8 @@
         CSSPrimitiveValue::UnitType right_type = right_side->ResolvedUnitType();
         if (left_type == right_type) {
           return CSSMathExpressionNumericLiteral::Create(
-              EvaluateOperator(left_side->DoubleValue(),
-                               right_side->DoubleValue(), op),
+              EvaluateOperator(
+                  {left_side->DoubleValue(), right_side->DoubleValue()}, op),
               left_type);
         }
         CSSPrimitiveValue::UnitCategory left_unit_category =
@@ -489,7 +487,8 @@
                 CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
                     right_type));
             return CSSMathExpressionNumericLiteral::Create(
-                EvaluateOperator(left_value, right_value, op), canonical_type);
+                EvaluateOperator({left_value, right_value}, op),
+                canonical_type);
           }
         }
       }
@@ -500,7 +499,7 @@
     const CSSMathExpressionNode* number_side =
         GetNumberSide(left_side, right_side);
     if (!number_side)
-      return Create(left_side, right_side, op);
+      return CreateArithmeticOperation(left_side, right_side, op);
     if (number_side == left_side && op == CSSMathOperator::kDivide)
       return nullptr;
     const CSSMathExpressionNode* other_side =
@@ -518,14 +517,15 @@
     CSSPrimitiveValue::UnitType other_type = other_side->ResolvedUnitType();
     if (HasDoubleValue(other_type)) {
       return CSSMathExpressionNumericLiteral::Create(
-          EvaluateOperator(other_side->DoubleValue(), number, op), other_type);
+          EvaluateOperator({other_side->DoubleValue(), number}, op),
+          other_type);
     }
   }
 
-  return Create(left_side, right_side, op);
+  return CreateArithmeticOperation(left_side, right_side, op);
 }
 
-CSSMathExpressionBinaryOperation::CSSMathExpressionBinaryOperation(
+CSSMathExpressionOperation::CSSMathExpressionOperation(
     const CSSMathExpressionNode* left_side,
     const CSSMathExpressionNode* right_side,
     CSSMathOperator op,
@@ -533,40 +533,46 @@
     : CSSMathExpressionNode(
           category,
           left_side->HasComparisons() || right_side->HasComparisons()),
-      left_side_(left_side),
-      right_side_(right_side),
+      operands_({left_side, right_side}),
       operator_(op) {}
 
-CSSMathExpressionVariadicOperation::CSSMathExpressionVariadicOperation(
+static bool AnyOperandHasComparisons(
+    CSSMathExpressionOperation::Operands& operands) {
+  for (const auto& operand : operands) {
+    if (operand->HasComparisons())
+      return true;
+  }
+  return false;
+}
+
+CSSMathExpressionOperation::CSSMathExpressionOperation(
     CalculationCategory category,
     Operands&& operands,
     CSSMathOperator op)
-    : CSSMathExpressionNode(category, true /* has_comparisons */),
+    : CSSMathExpressionNode(
+          category,
+          IsComparison(op) || AnyOperandHasComparisons(operands)),
       operands_(std::move(operands)),
       operator_(op) {}
 
-bool CSSMathExpressionBinaryOperation::IsZero() const {
-  return !DoubleValue();
-}
-
-bool CSSMathExpressionVariadicOperation::IsZero() const {
+bool CSSMathExpressionOperation::IsZero() const {
   absl::optional<double> maybe_value = ComputeValueInCanonicalUnit();
   return maybe_value && !*maybe_value;
 }
 
-absl::optional<PixelsAndPercent>
-CSSMathExpressionBinaryOperation::ToPixelsAndPercent(
+absl::optional<PixelsAndPercent> CSSMathExpressionOperation::ToPixelsAndPercent(
     const CSSToLengthConversionData& conversion_data) const {
   absl::optional<PixelsAndPercent> result;
   switch (operator_) {
     case CSSMathOperator::kAdd:
     case CSSMathOperator::kSubtract: {
-      result = left_side_->ToPixelsAndPercent(conversion_data);
+      DCHECK_EQ(operands_.size(), 2u);
+      result = operands_[0]->ToPixelsAndPercent(conversion_data);
       if (!result)
         return absl::nullopt;
 
       absl::optional<PixelsAndPercent> other_side =
-          right_side_->ToPixelsAndPercent(conversion_data);
+          operands_[1]->ToPixelsAndPercent(conversion_data);
       if (!other_side)
         return absl::nullopt;
       if (operator_ == CSSMathOperator::kAdd) {
@@ -580,10 +586,11 @@
     }
     case CSSMathOperator::kMultiply:
     case CSSMathOperator::kDivide: {
+      DCHECK_EQ(operands_.size(), 2u);
       const CSSMathExpressionNode* number_side =
-          GetNumberSide(left_side_, right_side_);
+          GetNumberSide(operands_[0], operands_[1]);
       const CSSMathExpressionNode* other_side =
-          left_side_ == number_side ? right_side_ : left_side_;
+          operands_[0] == number_side ? operands_[1] : operands_[0];
       result = other_side->ToPixelsAndPercent(conversion_data);
       if (!result)
         return absl::nullopt;
@@ -594,90 +601,85 @@
       result->percent *= number;
       break;
     }
-    default:
+    case CSSMathOperator::kMin:
+    case CSSMathOperator::kMax:
+      return absl::nullopt;
+    case CSSMathOperator::kInvalid:
       NOTREACHED();
   }
   return result;
 }
 
-absl::optional<PixelsAndPercent>
-CSSMathExpressionVariadicOperation::ToPixelsAndPercent(
-    const CSSToLengthConversionData& conversion_data) const {
-  return absl::nullopt;
-}
-
 scoped_refptr<const CalculationExpressionNode>
-CSSMathExpressionBinaryOperation::ToCalculationExpression(
+CSSMathExpressionOperation::ToCalculationExpression(
     const CSSToLengthConversionData& conversion_data) const {
   switch (operator_) {
     case CSSMathOperator::kAdd:
+      DCHECK_EQ(operands_.size(), 2u);
       return CalculationExpressionOperationNode::CreateSimplified(
           CalculationExpressionOperationNode::Children(
-              {left_side_->ToCalculationExpression(conversion_data),
-               right_side_->ToCalculationExpression(conversion_data)}),
+              {operands_[0]->ToCalculationExpression(conversion_data),
+               operands_[1]->ToCalculationExpression(conversion_data)}),
           CalculationOperator::kAdd);
     case CSSMathOperator::kSubtract:
+      DCHECK_EQ(operands_.size(), 2u);
       return CalculationExpressionOperationNode::CreateSimplified(
           CalculationExpressionOperationNode::Children(
-              {left_side_->ToCalculationExpression(conversion_data),
-               right_side_->ToCalculationExpression(conversion_data)}),
+              {operands_[0]->ToCalculationExpression(conversion_data),
+               operands_[1]->ToCalculationExpression(conversion_data)}),
           CalculationOperator::kSubtract);
     case CSSMathOperator::kMultiply:
-      DCHECK_NE((left_side_->Category() == kCalcNumber),
-                (right_side_->Category() == kCalcNumber));
-      if (left_side_->Category() == kCalcNumber) {
+      DCHECK_EQ(operands_.size(), 2u);
+      DCHECK_NE((operands_[0]->Category() == kCalcNumber),
+                (operands_[1]->Category() == kCalcNumber));
+      if (operands_[0]->Category() == kCalcNumber) {
         return CalculationExpressionOperationNode::CreateSimplified(
             CalculationExpressionOperationNode::Children(
-                {right_side_->ToCalculationExpression(conversion_data),
+                {operands_[1]->ToCalculationExpression(conversion_data),
                  base::MakeRefCounted<CalculationExpressionNumberNode>(
-                     left_side_->DoubleValue())}),
+                     operands_[0]->DoubleValue())}),
             CalculationOperator::kMultiply);
       }
       return CalculationExpressionOperationNode::CreateSimplified(
           CalculationExpressionOperationNode::Children(
-              {left_side_->ToCalculationExpression(conversion_data),
+              {operands_[0]->ToCalculationExpression(conversion_data),
                base::MakeRefCounted<CalculationExpressionNumberNode>(
-                   right_side_->DoubleValue())}),
+                   operands_[1]->DoubleValue())}),
           CalculationOperator::kMultiply);
     case CSSMathOperator::kDivide:
-      DCHECK_EQ(right_side_->Category(), kCalcNumber);
+      DCHECK_EQ(operands_.size(), 2u);
+      DCHECK_EQ(operands_[1]->Category(), kCalcNumber);
       return CalculationExpressionOperationNode::CreateSimplified(
           CalculationExpressionOperationNode::Children(
-              {left_side_->ToCalculationExpression(conversion_data),
+              {operands_[0]->ToCalculationExpression(conversion_data),
                base::MakeRefCounted<CalculationExpressionNumberNode>(
-                   1.0 / right_side_->DoubleValue())}),
+                   1.0 / operands_[1]->DoubleValue())}),
           CalculationOperator::kMultiply);
-    default:
+    case CSSMathOperator::kMin:
+    case CSSMathOperator::kMax: {
+      Vector<scoped_refptr<const CalculationExpressionNode>> operands;
+      operands.ReserveCapacity(operands_.size());
+      for (const auto& operand : operands_)
+        operands.push_back(operand->ToCalculationExpression(conversion_data));
+      auto expression_operator = operator_ == CSSMathOperator::kMin
+                                     ? CalculationOperator::kMin
+                                     : CalculationOperator::kMax;
+      return CalculationExpressionOperationNode::CreateSimplified(
+          std::move(operands), expression_operator);
+    }
+    case CSSMathOperator::kInvalid:
       NOTREACHED();
       return nullptr;
   }
 }
 
-scoped_refptr<const CalculationExpressionNode>
-CSSMathExpressionVariadicOperation::ToCalculationExpression(
-    const CSSToLengthConversionData& data) const {
-  Vector<scoped_refptr<const CalculationExpressionNode>> operands;
-  operands.ReserveCapacity(operands_.size());
-  for (const auto& operand : operands_)
-    operands.push_back(operand->ToCalculationExpression(data));
-  auto expression_operator = operator_ == CSSMathOperator::kMin
-                                 ? CalculationOperator::kMin
-                                 : CalculationOperator::kMax;
-  return CalculationExpressionOperationNode::CreateSimplified(
-      std::move(operands), expression_operator);
-}
-
-double CSSMathExpressionBinaryOperation::DoubleValue() const {
+double CSSMathExpressionOperation::DoubleValue() const {
   DCHECK(HasDoubleValue(ResolvedUnitType())) << CustomCSSText();
-  return Evaluate(left_side_->DoubleValue(), right_side_->DoubleValue());
-}
-
-double CSSMathExpressionVariadicOperation::DoubleValue() const {
-  DCHECK(HasDoubleValue(ResolvedUnitType()));
-  double result = operands_.front()->DoubleValue();
-  for (const auto& operand : SecondToLastOperands())
-    result = EvaluateBinary(result, operand->DoubleValue());
-  return result;
+  Vector<double> double_values;
+  double_values.ReserveCapacity(operands_.size());
+  for (auto& operand : operands_)
+    double_values.push_back(operand->DoubleValue());
+  return Evaluate(double_values);
 }
 
 static bool HasCanonicalUnit(CalculationCategory category) {
@@ -686,134 +688,92 @@
          category == kCalcTime || category == kCalcFrequency;
 }
 
-absl::optional<double>
-CSSMathExpressionBinaryOperation::ComputeValueInCanonicalUnit() const {
+absl::optional<double> CSSMathExpressionOperation::ComputeValueInCanonicalUnit()
+    const {
   if (!HasCanonicalUnit(category_))
     return absl::nullopt;
 
-  absl::optional<double> left_value = left_side_->ComputeValueInCanonicalUnit();
-  if (!left_value)
-    return absl::nullopt;
-
-  absl::optional<double> right_value =
-      right_side_->ComputeValueInCanonicalUnit();
-  if (!right_value)
-    return absl::nullopt;
-
-  return Evaluate(*left_value, *right_value);
-}
-
-absl::optional<double>
-CSSMathExpressionVariadicOperation::ComputeValueInCanonicalUnit() const {
-  absl::optional<double> first_value =
-      operands_.front()->ComputeValueInCanonicalUnit();
-  if (!first_value)
-    return absl::nullopt;
-
-  double result = *first_value;
-  for (const auto& operand : SecondToLastOperands()) {
+  Vector<double> double_values;
+  double_values.ReserveCapacity(operands_.size());
+  for (auto& operand : operands_) {
     absl::optional<double> maybe_value = operand->ComputeValueInCanonicalUnit();
     if (!maybe_value)
       return absl::nullopt;
-    result = EvaluateBinary(result, *maybe_value);
+    double_values.push_back(*maybe_value);
   }
-  return result;
+  return Evaluate(double_values);
 }
 
-double CSSMathExpressionBinaryOperation::ComputeLengthPx(
-    const CSSToLengthConversionData& conversion_data) const {
-  DCHECK_EQ(kCalcLength, Category());
-  double left_value;
-  if (left_side_->Category() == kCalcLength) {
-    left_value = left_side_->ComputeLengthPx(conversion_data);
-  } else {
-    DCHECK_EQ(kCalcNumber, left_side_->Category());
-    left_value = left_side_->DoubleValue();
-  }
-  double right_value;
-  if (right_side_->Category() == kCalcLength) {
-    right_value = right_side_->ComputeLengthPx(conversion_data);
-  } else {
-    DCHECK_EQ(kCalcNumber, right_side_->Category());
-    right_value = right_side_->DoubleValue();
-  }
-  return Evaluate(left_value, right_value);
-}
-
-double CSSMathExpressionVariadicOperation::ComputeLengthPx(
+double CSSMathExpressionOperation::ComputeLengthPx(
     const CSSToLengthConversionData& data) const {
   DCHECK_EQ(kCalcLength, Category());
-  double result = operands_.front()->ComputeLengthPx(data);
-  for (const auto& operand : SecondToLastOperands())
-    result = EvaluateBinary(result, operand->ComputeLengthPx(data));
-  return result;
+  Vector<double> double_values;
+  double_values.ReserveCapacity(operands_.size());
+  for (const auto& operand : operands_) {
+    if (operand->Category() == kCalcLength) {
+      double_values.push_back(operand->ComputeLengthPx(data));
+    } else {
+      DCHECK_EQ(operand->Category(), kCalcNumber);
+      double_values.push_back(operand->DoubleValue());
+    }
+  }
+  return Evaluate(double_values);
 }
 
-bool CSSMathExpressionBinaryOperation::AccumulateLengthArray(
+bool CSSMathExpressionOperation::AccumulateLengthArray(
     CSSLengthArray& length_array,
     double multiplier) const {
   switch (operator_) {
     case CSSMathOperator::kAdd:
-      if (!left_side_->AccumulateLengthArray(length_array, multiplier))
+      DCHECK_EQ(operands_.size(), 2u);
+      if (!operands_[0]->AccumulateLengthArray(length_array, multiplier))
         return false;
-      if (!right_side_->AccumulateLengthArray(length_array, multiplier))
+      if (!operands_[1]->AccumulateLengthArray(length_array, multiplier))
         return false;
       return true;
     case CSSMathOperator::kSubtract:
-      if (!left_side_->AccumulateLengthArray(length_array, multiplier))
+      DCHECK_EQ(operands_.size(), 2u);
+      if (!operands_[0]->AccumulateLengthArray(length_array, multiplier))
         return false;
-      if (!right_side_->AccumulateLengthArray(length_array, -multiplier))
+      if (!operands_[1]->AccumulateLengthArray(length_array, -multiplier))
         return false;
       return true;
     case CSSMathOperator::kMultiply:
-      DCHECK_NE((left_side_->Category() == kCalcNumber),
-                (right_side_->Category() == kCalcNumber));
-      if (left_side_->Category() == kCalcNumber) {
-        return right_side_->AccumulateLengthArray(
-            length_array, multiplier * left_side_->DoubleValue());
+      DCHECK_EQ(operands_.size(), 2u);
+      DCHECK_NE((operands_[0]->Category() == kCalcNumber),
+                (operands_[1]->Category() == kCalcNumber));
+      if (operands_[0]->Category() == kCalcNumber) {
+        return operands_[1]->AccumulateLengthArray(
+            length_array, multiplier * operands_[0]->DoubleValue());
       } else {
-        return left_side_->AccumulateLengthArray(
-            length_array, multiplier * right_side_->DoubleValue());
+        return operands_[0]->AccumulateLengthArray(
+            length_array, multiplier * operands_[1]->DoubleValue());
       }
     case CSSMathOperator::kDivide:
-      DCHECK_EQ(right_side_->Category(), kCalcNumber);
-      return left_side_->AccumulateLengthArray(
-          length_array, multiplier / right_side_->DoubleValue());
-    default:
+      DCHECK_EQ(operands_.size(), 2u);
+      DCHECK_EQ(operands_[1]->Category(), kCalcNumber);
+      return operands_[0]->AccumulateLengthArray(
+          length_array, multiplier / operands_[1]->DoubleValue());
+    case CSSMathOperator::kMin:
+    case CSSMathOperator::kMax:
+      // When comparison functions are involved, we can't resolve the expression
+      // into a length array.
+      return false;
+    case CSSMathOperator::kInvalid:
       NOTREACHED();
       return false;
   }
 }
 
-bool CSSMathExpressionVariadicOperation::AccumulateLengthArray(CSSLengthArray&,
-                                                               double) const {
-  // When comparison function are involved, we can't resolve the expression into
-  // a length array.
-  // TODO(crbug.com/991672): We need a more general length interpolation
-  // implementation that doesn't rely on CSSLengthArray.
-  return false;
-}
-
-void CSSMathExpressionBinaryOperation::AccumulateLengthUnitTypes(
-    CSSPrimitiveValue::LengthTypeFlags& types) const {
-  left_side_->AccumulateLengthUnitTypes(types);
-  right_side_->AccumulateLengthUnitTypes(types);
-}
-
-void CSSMathExpressionVariadicOperation::AccumulateLengthUnitTypes(
+void CSSMathExpressionOperation::AccumulateLengthUnitTypes(
     CSSPrimitiveValue::LengthTypeFlags& types) const {
   for (const auto& operand : operands_)
     operand->AccumulateLengthUnitTypes(types);
 }
 
-bool CSSMathExpressionBinaryOperation::IsComputationallyIndependent() const {
+bool CSSMathExpressionOperation::IsComputationallyIndependent() const {
   if (Category() != kCalcLength && Category() != kCalcPercentLength)
     return true;
-  return left_side_->IsComputationallyIndependent() &&
-         right_side_->IsComputationallyIndependent();
-}
-
-bool CSSMathExpressionVariadicOperation::IsComputationallyIndependent() const {
   for (const auto& operand : operands_) {
     if (!operand->IsComputationallyIndependent())
       return false;
@@ -821,55 +781,74 @@
   return true;
 }
 
-String CSSMathExpressionBinaryOperation::CustomCSSText() const {
-  StringBuilder result;
+String CSSMathExpressionOperation::CustomCSSText() const {
+  switch (operator_) {
+    case CSSMathOperator::kAdd:
+    case CSSMathOperator::kSubtract:
+    case CSSMathOperator::kMultiply:
+    case CSSMathOperator::kDivide: {
+      DCHECK_EQ(operands_.size(), 2u);
+      StringBuilder result;
 
-  const bool left_side_needs_parentheses =
-      left_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd;
-  if (left_side_needs_parentheses)
-    result.Append('(');
-  result.Append(left_side_->CustomCSSText());
-  if (left_side_needs_parentheses)
-    result.Append(')');
+      const bool left_side_needs_parentheses =
+          (operands_[0]->IsOperation() && !operands_[0]->IsMathFunction()) &&
+          operator_ != CSSMathOperator::kAdd;
+      if (left_side_needs_parentheses)
+        result.Append('(');
+      result.Append(operands_[0]->CustomCSSText());
+      if (left_side_needs_parentheses)
+        result.Append(')');
 
-  result.Append(' ');
-  result.Append(ToString(operator_));
-  result.Append(' ');
+      result.Append(' ');
+      result.Append(ToString(operator_));
+      result.Append(' ');
 
-  const bool right_side_needs_parentheses =
-      right_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd;
-  if (right_side_needs_parentheses)
-    result.Append('(');
-  result.Append(right_side_->CustomCSSText());
-  if (right_side_needs_parentheses)
-    result.Append(')');
+      const bool right_side_needs_parentheses =
+          (operands_[1]->IsOperation() && !operands_[1]->IsMathFunction()) &&
+          operator_ != CSSMathOperator::kAdd;
+      if (right_side_needs_parentheses)
+        result.Append('(');
+      result.Append(operands_[1]->CustomCSSText());
+      if (right_side_needs_parentheses)
+        result.Append(')');
 
-  return result.ReleaseString();
-}
+      return result.ReleaseString();
+    }
+    case CSSMathOperator::kMin:
+    case CSSMathOperator::kMax: {
+      // TODO(pjh0718): Change clamp representation from max(min()) to single
+      // clamp() node and remove CSSTextAsClamp().
+      if (is_clamp_)
+        return CSSTextAsClamp();
 
-String CSSMathExpressionVariadicOperation::CustomCSSText() const {
-  if (is_clamp_)
-    return CSSTextAsClamp();
+      StringBuilder result;
+      result.Append(ToString(operator_));
+      result.Append('(');
+      result.Append(operands_.front()->CustomCSSText());
+      for (const auto& operand : SecondToLastOperands()) {
+        result.Append(", ");
+        result.Append(operand->CustomCSSText());
+      }
+      result.Append(')');
 
-  StringBuilder result;
-  result.Append(ToString(operator_));
-  result.Append('(');
-  result.Append(operands_.front()->CustomCSSText());
-  for (const auto& operand : SecondToLastOperands()) {
-    result.Append(", ");
-    result.Append(operand->CustomCSSText());
+      return result.ReleaseString();
+    }
+    case CSSMathOperator::kInvalid:
+      NOTREACHED();
+      return String();
   }
-  result.Append(')');
-
-  return result.ReleaseString();
 }
 
-String CSSMathExpressionVariadicOperation::CSSTextAsClamp() const {
+String CSSMathExpressionOperation::CSSTextAsClamp() const {
   DCHECK(is_clamp_);
   DCHECK_EQ(CSSMathOperator::kMax, operator_);
   DCHECK_EQ(2u, operands_.size());
-  DCHECK(operands_[1]->IsVariadicOperation());
-  const auto& nested = To<CSSMathExpressionVariadicOperation>(*operands_[1]);
+  // TODO(pjh0718): Actually we should IsMinOrMax() check here,
+  // but currently it is not a virtual function and CSSTextAsClamp() will be
+  // removed anyway during changing clamp representation from max(min()) to
+  // single clamp() node.
+  DCHECK(operands_[1]->IsMathFunction());
+  const auto& nested = To<CSSMathExpressionOperation>(*operands_[1]);
   DCHECK(!nested.is_clamp_);
   DCHECK_EQ(CSSMathOperator::kMin, nested.operator_);
   DCHECK_EQ(2u, nested.operands_.size());
@@ -885,24 +864,12 @@
   return result.ReleaseString();
 }
 
-bool CSSMathExpressionBinaryOperation::operator==(
+bool CSSMathExpressionOperation::operator==(
     const CSSMathExpressionNode& exp) const {
-  if (!exp.IsBinaryOperation())
+  if (!exp.IsOperation())
     return false;
 
-  const CSSMathExpressionBinaryOperation& other =
-      To<CSSMathExpressionBinaryOperation>(exp);
-  return base::ValuesEquivalent(left_side_, other.left_side_) &&
-         base::ValuesEquivalent(right_side_, other.right_side_) &&
-         operator_ == other.operator_;
-}
-
-bool CSSMathExpressionVariadicOperation::operator==(
-    const CSSMathExpressionNode& exp) const {
-  if (!exp.IsVariadicOperation())
-    return false;
-  const CSSMathExpressionVariadicOperation& other =
-      To<CSSMathExpressionVariadicOperation>(exp);
+  const CSSMathExpressionOperation& other = To<CSSMathExpressionOperation>(exp);
   if (operator_ != other.operator_)
     return false;
   if (operands_.size() != other.operands_.size())
@@ -914,21 +881,39 @@
   return true;
 }
 
-CSSPrimitiveValue::UnitType CSSMathExpressionBinaryOperation::ResolvedUnitType()
+CSSPrimitiveValue::UnitType CSSMathExpressionOperation::ResolvedUnitType()
     const {
+  // TODO(pjh0718): Merge this if statement into switch-case below.
+  if (IsMinOrMax()) {
+    if (category_ == kCalcNumber)
+      return CSSPrimitiveValue::UnitType::kNumber;
+
+    CSSPrimitiveValue::UnitType result = operands_.front()->ResolvedUnitType();
+    if (result == CSSPrimitiveValue::UnitType::kUnknown)
+      return CSSPrimitiveValue::UnitType::kUnknown;
+    for (const auto& operand : SecondToLastOperands()) {
+      CSSPrimitiveValue::UnitType next = operand->ResolvedUnitType();
+      if (next == CSSPrimitiveValue::UnitType::kUnknown || next != result)
+        return CSSPrimitiveValue::UnitType::kUnknown;
+    }
+    return result;
+  }
+
+  DCHECK_EQ(operands_.size(), 2u);
+
   switch (category_) {
     case kCalcNumber:
-      DCHECK_EQ(left_side_->Category(), kCalcNumber);
-      DCHECK_EQ(right_side_->Category(), kCalcNumber);
+      DCHECK_EQ(operands_[0]->Category(), kCalcNumber);
+      DCHECK_EQ(operands_[1]->Category(), kCalcNumber);
       return CSSPrimitiveValue::UnitType::kNumber;
     case kCalcLength:
     case kCalcPercent: {
-      if (left_side_->Category() == kCalcNumber)
-        return right_side_->ResolvedUnitType();
-      if (right_side_->Category() == kCalcNumber)
-        return left_side_->ResolvedUnitType();
-      CSSPrimitiveValue::UnitType left_type = left_side_->ResolvedUnitType();
-      if (left_type == right_side_->ResolvedUnitType())
+      if (operands_[0]->Category() == kCalcNumber)
+        return operands_[1]->ResolvedUnitType();
+      if (operands_[1]->Category() == kCalcNumber)
+        return operands_[0]->ResolvedUnitType();
+      CSSPrimitiveValue::UnitType left_type = operands_[0]->ResolvedUnitType();
+      if (left_type == operands_[1]->ResolvedUnitType())
         return left_type;
       return CSSPrimitiveValue::UnitType::kUnknown;
     }
@@ -942,39 +927,18 @@
     case kCalcOther:
       return CSSPrimitiveValue::UnitType::kUnknown;
   }
+
   NOTREACHED();
   return CSSPrimitiveValue::UnitType::kUnknown;
 }
 
-CSSPrimitiveValue::UnitType
-CSSMathExpressionVariadicOperation::ResolvedUnitType() const {
-  if (Category() == kCalcNumber)
-    return CSSPrimitiveValue::UnitType::kNumber;
-
-  CSSPrimitiveValue::UnitType result = operands_.front()->ResolvedUnitType();
-  if (result == CSSPrimitiveValue::UnitType::kUnknown)
-    return CSSPrimitiveValue::UnitType::kUnknown;
-  for (const auto& operand : SecondToLastOperands()) {
-    CSSPrimitiveValue::UnitType next = operand->ResolvedUnitType();
-    if (next == CSSPrimitiveValue::UnitType::kUnknown || next != result)
-      return CSSPrimitiveValue::UnitType::kUnknown;
-  }
-  return result;
-}
-
-void CSSMathExpressionBinaryOperation::Trace(Visitor* visitor) const {
-  visitor->Trace(left_side_);
-  visitor->Trace(right_side_);
-  CSSMathExpressionNode::Trace(visitor);
-}
-
-void CSSMathExpressionVariadicOperation::Trace(Visitor* visitor) const {
+void CSSMathExpressionOperation::Trace(Visitor* visitor) const {
   visitor->Trace(operands_);
   CSSMathExpressionNode::Trace(visitor);
 }
 
 // static
-const CSSMathExpressionNode* CSSMathExpressionBinaryOperation::GetNumberSide(
+const CSSMathExpressionNode* CSSMathExpressionOperation::GetNumberSide(
     const CSSMathExpressionNode* left_side,
     const CSSMathExpressionNode* right_side) {
   if (left_side->Category() == kCalcNumber)
@@ -985,60 +949,59 @@
 }
 
 // static
-double CSSMathExpressionBinaryOperation::EvaluateOperator(double left_value,
-                                                          double right_value,
-                                                          CSSMathOperator op) {
+double CSSMathExpressionOperation::EvaluateOperator(
+    const Vector<double>& operands,
+    CSSMathOperator op) {
   // Design doc for infinity and NaN: https://bit.ly/349gXjq
   switch (op) {
     case CSSMathOperator::kAdd:
+      DCHECK_EQ(operands.size(), 2u);
       if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
-        return left_value + right_value;
-      return ClampTo<double>(left_value + right_value);
+        return operands[0] + operands[1];
+      return ClampTo<double>(operands[0] + operands[1]);
     case CSSMathOperator::kSubtract:
+      DCHECK_EQ(operands.size(), 2u);
       if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
-        return left_value - right_value;
-      return ClampTo<double>(left_value - right_value);
+        return operands[0] - operands[1];
+      return ClampTo<double>(operands[0] - operands[1]);
     case CSSMathOperator::kMultiply:
+      DCHECK_EQ(operands.size(), 2u);
       if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
-        return left_value * right_value;
-      return ClampTo<double>(left_value * right_value);
+        return operands[0] * operands[1];
+      return ClampTo<double>(operands[0] * operands[1]);
     case CSSMathOperator::kDivide:
+      DCHECK(operands.size() == 1u || operands.size() == 2u);
       if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
-        return left_value / right_value;
-      if (right_value)
-        return ClampTo<double>(left_value / right_value);
+        return operands[0] / operands[1];
+      if (operands[1])
+        return ClampTo<double>(operands[0] / operands[1]);
       return std::numeric_limits<double>::quiet_NaN();
-    default:
+    case CSSMathOperator::kMin: {
+      if (operands.IsEmpty())
+        return std::numeric_limits<double>::quiet_NaN();
+      double minimum = operands[0];
+      for (auto operand : operands)
+        minimum = std::min(minimum, operand);
+      return minimum;
+    }
+    case CSSMathOperator::kMax: {
+      if (operands.IsEmpty())
+        return std::numeric_limits<double>::quiet_NaN();
+      double maximum = operands[0];
+      for (auto operand : operands)
+        maximum = std::max(maximum, operand);
+      return maximum;
+    }
+    case CSSMathOperator::kInvalid:
       NOTREACHED();
       break;
   }
   return 0;
 }
 
-double CSSMathExpressionVariadicOperation::EvaluateBinary(double lhs,
-                                                          double rhs) const {
-  if (std::isnan(lhs) || std::isnan(rhs))
-    return std::numeric_limits<double>::quiet_NaN();
-
-  switch (operator_) {
-    case CSSMathOperator::kMin:
-      return std::min(lhs, rhs);
-    case CSSMathOperator::kMax:
-      return std::max(lhs, rhs);
-    default:
-      NOTREACHED();
-      return 0;
-  }
-}
-
 #if DCHECK_IS_ON()
-bool CSSMathExpressionBinaryOperation::InvolvesPercentageComparisons() const {
-  return left_side_->InvolvesPercentageComparisons() ||
-         right_side_->InvolvesPercentageComparisons();
-}
-
-bool CSSMathExpressionVariadicOperation::InvolvesPercentageComparisons() const {
-  if (Category() == kCalcPercent && operands_.size() > 1u)
+bool CSSMathExpressionOperation::InvolvesPercentageComparisons() const {
+  if (IsMinOrMax() && Category() == kCalcPercent && operands_.size() > 1u)
     return true;
   for (const auto& operand : operands_) {
     if (operand->InvolvesPercentageComparisons())
@@ -1056,6 +1019,27 @@
  public:
   CSSMathExpressionNodeParser() {}
 
+  CSSMathExpressionNode* ParseMathFunction(CSSValueID function_id,
+                                           const CSSParserTokenRange& tokens,
+                                           int depth) {
+    switch (function_id) {
+      case CSSValueID::kCalc:
+      case CSSValueID::kWebkitCalc:
+        return ParseCalc(tokens);
+      case CSSValueID::kMin:
+        return ParseMinOrMax(tokens, CSSMathOperator::kMin, depth);
+      case CSSValueID::kMax:
+        return ParseMinOrMax(tokens, CSSMathOperator::kMax, depth);
+      case CSSValueID::kClamp:
+        return ParseClamp(tokens, depth);
+      // TODO(crbug.com/1284199): Support other math functions.
+      default:
+        return nullptr;
+    }
+  }
+
+  // TODO(pjh0718) : Integrate ParseCalc and ParseMinOrMaxOrClamp to
+  // ParseMathFunction.
   CSSMathExpressionNode* ParseCalc(CSSParserTokenRange tokens) {
     tokens.ConsumeWhitespace();
     CSSMathExpressionNode* result = ParseValueExpression(tokens, 0);
@@ -1071,7 +1055,7 @@
     if (tokens.AtEnd())
       return nullptr;
 
-    CSSMathExpressionVariadicOperation::Operands operands;
+    CSSMathExpressionOperation::Operands operands;
     bool last_token_is_comma = false;
     while (!tokens.AtEnd()) {
       tokens.ConsumeWhitespace();
@@ -1090,7 +1074,8 @@
     if (operands.IsEmpty() || !tokens.AtEnd() || last_token_is_comma)
       return nullptr;
 
-    return CSSMathExpressionVariadicOperation::Create(std::move(operands), op);
+    return CSSMathExpressionOperation::CreateComparisonFunction(
+        std::move(operands), op);
   }
 
   CSSMathExpressionNode* ParseClamp(CSSParserTokenRange tokens, int depth) {
@@ -1120,12 +1105,12 @@
 
     // clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX))
 
-    auto* nested = CSSMathExpressionVariadicOperation::Create(
+    auto* nested = CSSMathExpressionOperation::CreateComparisonFunction(
         {val_operand, max_operand}, CSSMathOperator::kMin);
     if (!nested)
       return nullptr;
 
-    auto* result = CSSMathExpressionVariadicOperation::Create(
+    auto* result = CSSMathExpressionOperation::CreateComparisonFunction(
         {min_operand, nested}, CSSMathOperator::kMax);
     if (!result)
       return nullptr;
@@ -1225,7 +1210,7 @@
       if (!rhs)
         return nullptr;
 
-      result = CSSMathExpressionBinaryOperation::CreateSimplified(
+      result = CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           result, rhs, math_operator);
 
       if (!result)
@@ -1263,7 +1248,7 @@
       if (!rhs)
         return nullptr;
 
-      result = CSSMathExpressionBinaryOperation::CreateSimplified(
+      result = CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
           result, rhs, math_operator);
 
       if (!result)
@@ -1331,7 +1316,7 @@
     pixels = -pixels;
     op = CSSMathOperator::kSubtract;
   }
-  return CSSMathExpressionBinaryOperation::Create(
+  return CSSMathExpressionOperation::CreateArithmeticOperation(
       CSSMathExpressionNumericLiteral::Create(CSSNumericLiteralValue::Create(
           percent, CSSPrimitiveValue::UnitType::kPercentage)),
       CSSMathExpressionNumericLiteral::Create(CSSNumericLiteralValue::Create(
@@ -1361,7 +1346,7 @@
       auto& number_node = children[0]->IsNumber() ? children[0] : children[1];
       const auto& number = To<CalculationExpressionNumberNode>(*number_node);
       double number_value = number.Value();
-      return CSSMathExpressionBinaryOperation::Create(
+      return CSSMathExpressionOperation::CreateArithmeticOperation(
           Create(*pixels_and_percent_node),
           CSSMathExpressionNumericLiteral::Create(
               CSSNumericLiteralValue::Create(
@@ -1376,19 +1361,20 @@
       CSSMathOperator op = (calc_op == CalculationOperator::kAdd)
                                ? CSSMathOperator::kAdd
                                : CSSMathOperator::kSubtract;
-      return CSSMathExpressionBinaryOperation::Create(lhs, rhs, op);
+      return CSSMathExpressionOperation::CreateArithmeticOperation(lhs, rhs,
+                                                                   op);
     }
     case CalculationOperator::kMin:
     case CalculationOperator::kMax: {
       DCHECK(children.size());
-      CSSMathExpressionVariadicOperation::Operands operands;
+      CSSMathExpressionOperation::Operands operands;
       for (const auto& child : children)
         operands.push_back(Create(*child));
       CSSMathOperator op = (calc_op == CalculationOperator::kMin)
                                ? CSSMathOperator::kMin
                                : CSSMathOperator::kMax;
-      return CSSMathExpressionVariadicOperation::Create(std::move(operands),
-                                                        op);
+      return CSSMathExpressionOperation::CreateComparisonFunction(
+          std::move(operands), op);
     }
     default:
       NOTREACHED();
@@ -1404,24 +1390,15 @@
 }
 
 // static
-CSSMathExpressionNode* CSSMathExpressionNode::ParseMin(
+CSSMathExpressionNode* CSSMathExpressionNode::ParseMathFunction(
+    CSSValueID function_id,
     const CSSParserTokenRange& tokens) {
   CSSMathExpressionNodeParser parser;
-  return parser.ParseMinOrMax(tokens, CSSMathOperator::kMin, 0);
-}
+  CSSMathExpressionNode* result =
+      parser.ParseMathFunction(function_id, tokens, 0);
 
-// static
-CSSMathExpressionNode* CSSMathExpressionNode::ParseMax(
-    const CSSParserTokenRange& tokens) {
-  CSSMathExpressionNodeParser parser;
-  return parser.ParseMinOrMax(tokens, CSSMathOperator::kMax, 0);
-}
-
-// static
-CSSMathExpressionNode* CSSMathExpressionNode::ParseClamp(
-    const CSSParserTokenRange& tokens) {
-  CSSMathExpressionNodeParser parser;
-  return parser.ParseClamp(tokens, 0);
+  // TODO(pjh0718): Do simplificiation for result above.
+  return result;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.h b/third_party/blink/renderer/core/css/css_math_expression_node.h
index c02c4ca..f51deaba 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.h
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.h
@@ -69,17 +69,18 @@
   static CSSMathExpressionNode* Create(const CalculationExpressionNode& node);
 
   static CSSMathExpressionNode* ParseCalc(const CSSParserTokenRange& tokens);
-  static CSSMathExpressionNode* ParseMin(const CSSParserTokenRange& tokens);
-  static CSSMathExpressionNode* ParseMax(const CSSParserTokenRange& tokens);
-  static CSSMathExpressionNode* ParseClamp(const CSSParserTokenRange& tokens);
+  static CSSMathExpressionNode* ParseMathFunction(
+      CSSValueID function_id,
+      const CSSParserTokenRange& tokens);
 
   virtual bool IsNumericLiteral() const { return false; }
-  virtual bool IsBinaryOperation() const { return false; }
-  virtual bool IsVariadicOperation() const { return false; }
+  virtual bool IsOperation() const { return false; }
 
-  bool IsMathFunction() const {
-    return !IsNumericLiteral() && !IsBinaryOperation();
-  }
+  virtual bool IsMathFunction() const { return false; }
+
+  // TODO(pjh0718): Change clamp representation from max(min()) to single
+  // clamp() node and remove this clamp specific method.
+  virtual void SetIsClamp() = 0;
 
   virtual bool IsZero() const = 0;
 
@@ -172,6 +173,10 @@
 
   bool IsNumericLiteral() const final { return true; }
 
+  // TODO(pjh0718): Change clamp representation from max(min()) to single
+  // clamp() node and remove this clamp specific method.
+  void SetIsClamp() final {}
+
   bool IsZero() const final;
   String CustomCSSText() const final;
   scoped_refptr<const CalculationExpressionNode> ToCalculationExpression(
@@ -206,29 +211,49 @@
   }
 };
 
-class CORE_EXPORT CSSMathExpressionBinaryOperation final
+class CORE_EXPORT CSSMathExpressionOperation final
     : public CSSMathExpressionNode {
  public:
-  static CSSMathExpressionNode* Create(const CSSMathExpressionNode* left_side,
-                                       const CSSMathExpressionNode* right_side,
-                                       CSSMathOperator op);
-  static CSSMathExpressionNode* CreateSimplified(
+  using Operands = HeapVector<Member<const CSSMathExpressionNode>>;
+
+  static CSSMathExpressionNode* CreateArithmeticOperation(
       const CSSMathExpressionNode* left_side,
       const CSSMathExpressionNode* right_side,
       CSSMathOperator op);
 
-  CSSMathExpressionBinaryOperation(const CSSMathExpressionNode* left_side,
-                                   const CSSMathExpressionNode* right_side,
-                                   CSSMathOperator op,
-                                   CalculationCategory category);
+  static CSSMathExpressionNode* CreateComparisonFunction(Operands&& operands,
+                                                         CSSMathOperator op);
 
-  const CSSMathExpressionNode* LeftExpressionNode() const { return left_side_; }
-  const CSSMathExpressionNode* RightExpressionNode() const {
-    return right_side_;
-  }
+  static CSSMathExpressionNode* CreateArithmeticOperationSimplified(
+      const CSSMathExpressionNode* left_side,
+      const CSSMathExpressionNode* right_side,
+      CSSMathOperator op);
+
+  CSSMathExpressionOperation(const CSSMathExpressionNode* left_side,
+                             const CSSMathExpressionNode* right_side,
+                             CSSMathOperator op,
+                             CalculationCategory category);
+
+  CSSMathExpressionOperation(CalculationCategory category,
+                             Operands&& operands,
+                             CSSMathOperator op);
+
+  const Operands& GetOperands() const { return operands_; }
   CSSMathOperator OperatorType() const { return operator_; }
 
-  bool IsBinaryOperation() const final { return true; }
+  bool IsOperation() const final { return true; }
+  bool IsMinOrMax() const {
+    return operator_ == CSSMathOperator::kMin ||
+           operator_ == CSSMathOperator::kMax;
+  }
+
+  // TODO(crbug.com/1284199): Check other math functions too(clamp, etc).
+  bool IsMathFunction() const final { return IsMinOrMax(); }
+
+  // TODO(pjh0718): Change clamp representation from max(min()) to single
+  // clamp() node and remove these clamp specific methods.
+  void SetIsClamp() final { is_clamp_ = true; }
+  String CSSTextAsClamp() const;
 
   bool IsZero() const final;
   scoped_refptr<const CalculationExpressionNode> ToCalculationExpression(
@@ -258,86 +283,30 @@
       const CSSMathExpressionNode* left_side,
       const CSSMathExpressionNode* right_side);
 
-  double Evaluate(double left_side, double right_side) const {
-    return EvaluateOperator(left_side, right_side, operator_);
+  double Evaluate(const Vector<double>& operands) const {
+    return EvaluateOperator(operands, operator_);
   }
 
-  static double EvaluateOperator(double left_value,
-                                 double right_value,
+  static double EvaluateOperator(const Vector<double>& operands,
                                  CSSMathOperator op);
 
-  const Member<const CSSMathExpressionNode> left_side_;
-  const Member<const CSSMathExpressionNode> right_side_;
-  const CSSMathOperator operator_;
-};
-
-template <>
-struct DowncastTraits<CSSMathExpressionBinaryOperation> {
-  static bool AllowFrom(const CSSMathExpressionNode& node) {
-    return node.IsBinaryOperation();
-  }
-};
-
-class CSSMathExpressionVariadicOperation final : public CSSMathExpressionNode {
- public:
-  using Operands = HeapVector<Member<const CSSMathExpressionNode>>;
-
-  static CSSMathExpressionVariadicOperation* Create(Operands&& operands,
-                                                    CSSMathOperator op);
-
-  CSSMathExpressionVariadicOperation(CalculationCategory category,
-                                     Operands&& operands,
-                                     CSSMathOperator op);
-
-  const Operands& GetOperands() const { return operands_; }
-  CSSMathOperator OperatorType() const { return operator_; }
-
-  bool IsVariadicOperation() const final { return true; }
-
-  void SetIsClamp() { is_clamp_ = true; }
-  String CSSTextAsClamp() const;
-
-  bool IsZero() const final;
-  String CustomCSSText() const final;
-  scoped_refptr<const CalculationExpressionNode> ToCalculationExpression(
-      const CSSToLengthConversionData&) const final;
-  absl::optional<PixelsAndPercent> ToPixelsAndPercent(
-      const CSSToLengthConversionData&) const final;
-  double DoubleValue() const final;
-  double ComputeLengthPx(
-      const CSSToLengthConversionData& conversion_data) const final;
-  bool AccumulateLengthArray(CSSLengthArray& length_array,
-                             double multiplier) const final;
-  void AccumulateLengthUnitTypes(
-      CSSPrimitiveValue::LengthTypeFlags& types) const final;
-  absl::optional<double> ComputeValueInCanonicalUnit() const final;
-  bool IsComputationallyIndependent() const final;
-  bool operator==(const CSSMathExpressionNode& other) const final;
-  CSSPrimitiveValue::UnitType ResolvedUnitType() const final;
-  void Trace(Visitor* visitor) const final;
-
-#if DCHECK_IS_ON()
-  bool InvolvesPercentageComparisons() const final;
-#endif
-
- private:
   // Helper for iterating from the 2nd to the last operands
   base::span<const Member<const CSSMathExpressionNode>> SecondToLastOperands()
       const {
     return base::make_span(std::next(operands_.begin()), operands_.end());
   }
 
-  double EvaluateBinary(double lhs, double rhs) const;
-
   Operands operands_;
   const CSSMathOperator operator_;
+  // TODO(pjh0718): Change clamp representation from max(min()) to single
+  // clamp() node and remove this clamp specific member;
   bool is_clamp_ = false;
 };
 
 template <>
-struct DowncastTraits<CSSMathExpressionVariadicOperation> {
+struct DowncastTraits<CSSMathExpressionOperation> {
   static bool AllowFrom(const CSSMathExpressionNode& node) {
-    return node.IsVariadicOperation();
+    return node.IsOperation();
   }
 };
 
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node_test.cc b/third_party/blink/renderer/core/css/css_math_expression_node_test.cc
index 3cfc3c2..b54b353 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node_test.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node_test.cc
@@ -105,7 +105,7 @@
 
   TestAccumulatePixelsAndPercent(
       conversion_data,
-      CSSMathExpressionBinaryOperation::Create(
+      CSSMathExpressionOperation::CreateArithmeticOperation(
           CSSMathExpressionNumericLiteral::Create(
               CSSNumericLiteralValue::Create(
                   10, CSSPrimitiveValue::UnitType::kPixels)),
@@ -117,7 +117,7 @@
 
   TestAccumulatePixelsAndPercent(
       conversion_data,
-      CSSMathExpressionBinaryOperation::Create(
+      CSSMathExpressionOperation::CreateArithmeticOperation(
           CSSMathExpressionNumericLiteral::Create(
               CSSNumericLiteralValue::Create(
                   1, CSSPrimitiveValue::UnitType::kInches)),
@@ -129,8 +129,8 @@
 
   TestAccumulatePixelsAndPercent(
       conversion_data,
-      CSSMathExpressionBinaryOperation::Create(
-          CSSMathExpressionBinaryOperation::Create(
+      CSSMathExpressionOperation::CreateArithmeticOperation(
+          CSSMathExpressionOperation::CreateArithmeticOperation(
               CSSMathExpressionNumericLiteral::Create(
                   CSSNumericLiteralValue::Create(
                       50, CSSPrimitiveValue::UnitType::kPixels)),
@@ -138,7 +138,7 @@
                   CSSNumericLiteralValue::Create(
                       0.25, CSSPrimitiveValue::UnitType::kNumber)),
               CSSMathOperator::kMultiply),
-          CSSMathExpressionBinaryOperation::Create(
+          CSSMathExpressionOperation::CreateArithmeticOperation(
               CSSMathExpressionNumericLiteral::Create(
                   CSSNumericLiteralValue::Create(
                       20, CSSPrimitiveValue::UnitType::kPixels)),
diff --git a/third_party/blink/renderer/core/css/css_math_operator.cc b/third_party/blink/renderer/core/css/css_math_operator.cc
index 91bc89a..4a58fe3 100644
--- a/third_party/blink/renderer/core/css/css_math_operator.cc
+++ b/third_party/blink/renderer/core/css/css_math_operator.cc
@@ -46,4 +46,8 @@
   }
 }
 
+bool IsComparison(CSSMathOperator op) {
+  return op == CSSMathOperator::kMin || op == CSSMathOperator::kMax;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_math_operator.h b/third_party/blink/renderer/core/css/css_math_operator.h
index ffe5738..9117113e 100644
--- a/third_party/blink/renderer/core/css/css_math_operator.h
+++ b/third_party/blink/renderer/core/css/css_math_operator.h
@@ -24,6 +24,8 @@
 CSSMathOperator ParseCSSArithmeticOperator(const CSSParserToken& token);
 String ToString(CSSMathOperator);
 
+bool IsComparison(CSSMathOperator);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_MATH_OPERATOR_H_
diff --git a/third_party/blink/renderer/core/css/css_primitive_value_test.cc b/third_party/blink/renderer/core/css/css_primitive_value_test.cc
index 9f40d57..f73f80fd 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value_test.cc
+++ b/third_party/blink/renderer/core/css/css_primitive_value_test.cc
@@ -33,15 +33,16 @@
 }
 
 CSSPrimitiveValue* CreateAddition(UnitValue a, UnitValue b) {
-  return CSSMathFunctionValue::Create(CSSMathExpressionBinaryOperation::Create(
-      CSSMathExpressionNumericLiteral::Create(Create(a)),
-      CSSMathExpressionNumericLiteral::Create(Create(b)),
-      CSSMathOperator::kAdd));
+  return CSSMathFunctionValue::Create(
+      CSSMathExpressionOperation::CreateArithmeticOperation(
+          CSSMathExpressionNumericLiteral::Create(Create(a)),
+          CSSMathExpressionNumericLiteral::Create(Create(b)),
+          CSSMathOperator::kAdd));
 }
 
 CSSPrimitiveValue* CreateNonNegativeSubtraction(UnitValue a, UnitValue b) {
   return CSSMathFunctionValue::Create(
-      CSSMathExpressionBinaryOperation::Create(
+      CSSMathExpressionOperation::CreateArithmeticOperation(
           CSSMathExpressionNumericLiteral::Create(Create(a)),
           CSSMathExpressionNumericLiteral::Create(Create(b)),
           CSSMathOperator::kSubtract),
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_invert.cc b/third_party/blink/renderer/core/css/cssom/css_math_invert.cc
index 4e7f257..68e2617 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_invert.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_math_invert.cc
@@ -47,7 +47,7 @@
   CSSMathExpressionNode* right_side = value_->ToCalcExpressionNode();
   if (!right_side)
     return nullptr;
-  return CSSMathExpressionBinaryOperation::Create(
+  return CSSMathExpressionOperation::CreateArithmeticOperation(
       CSSMathExpressionNumericLiteral::Create(
           1, CSSPrimitiveValue::UnitType::kNumber),
       right_side, CSSMathOperator::kDivide);
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_max.cc b/third_party/blink/renderer/core/css/cssom/css_math_max.cc
index c2fa7aa7..1f16f7f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_max.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_math_max.cc
@@ -72,7 +72,7 @@
 }
 
 CSSMathExpressionNode* CSSMathMax::ToCalcExpressionNode() const {
-  CSSMathExpressionVariadicOperation::Operands operands;
+  CSSMathExpressionOperation::Operands operands;
   operands.ReserveCapacity(NumericValues().size());
   for (const auto& value : NumericValues()) {
     CSSMathExpressionNode* operand = value->ToCalcExpressionNode();
@@ -90,8 +90,8 @@
     NOTREACHED();
     return nullptr;
   }
-  return CSSMathExpressionVariadicOperation::Create(std::move(operands),
-                                                    CSSMathOperator::kMax);
+  return CSSMathExpressionOperation::CreateComparisonFunction(
+      std::move(operands), CSSMathOperator::kMax);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_min.cc b/third_party/blink/renderer/core/css/cssom/css_math_min.cc
index 42d889f..733b6c50 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_min.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_math_min.cc
@@ -71,7 +71,7 @@
 }
 
 CSSMathExpressionNode* CSSMathMin::ToCalcExpressionNode() const {
-  CSSMathExpressionVariadicOperation::Operands operands;
+  CSSMathExpressionOperation::Operands operands;
   operands.ReserveCapacity(NumericValues().size());
   for (const auto& value : NumericValues()) {
     CSSMathExpressionNode* operand = value->ToCalcExpressionNode();
@@ -89,8 +89,8 @@
     NOTREACHED();
     return nullptr;
   }
-  return CSSMathExpressionVariadicOperation::Create(std::move(operands),
-                                                    CSSMathOperator::kMin);
+  return CSSMathExpressionOperation::CreateComparisonFunction(
+      std::move(operands), CSSMathOperator::kMin);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_negate.cc b/third_party/blink/renderer/core/css/cssom/css_math_negate.cc
index eba620bc..a3685b4 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_negate.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_math_negate.cc
@@ -42,7 +42,7 @@
   CSSMathExpressionNode* right_side = value_->ToCalcExpressionNode();
   if (!right_side)
     return nullptr;
-  return CSSMathExpressionBinaryOperation::CreateSimplified(
+  return CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
       CSSMathExpressionNumericLiteral::Create(
           -1, CSSPrimitiveValue::UnitType::kNumber),
       right_side, CSSMathOperator::kMultiply);
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_variadic.h b/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
index 8bc64cb40..ef26379 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
@@ -72,7 +72,8 @@
           NumericValues()[i]->ToCalcExpressionNode();
       if (!next_arg)
         return nullptr;
-      node = CSSMathExpressionBinaryOperation::Create(node, next_arg, op);
+      node = CSSMathExpressionOperation::CreateArithmeticOperation(
+          node, next_arg, op);
     }
     return node;
   }
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc b/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
index 60f87cb..6a42b7f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
@@ -91,15 +91,14 @@
 
 bool CanCombineNodes(const CSSMathExpressionNode& root,
                      const CSSMathExpressionNode& node) {
-  DCHECK(root.IsBinaryOperation());
-  if (!node.IsBinaryOperation())
+  DCHECK(root.IsOperation());
+  if (!node.IsOperation())
     return false;
   if (node.IsNestedCalc())
     return false;
   return CanonicalOperator(
-             To<CSSMathExpressionBinaryOperation>(root).OperatorType()) ==
-         CanonicalOperator(
-             To<CSSMathExpressionBinaryOperation>(node).OperatorType());
+             To<CSSMathExpressionOperation>(root).OperatorType()) ==
+         CanonicalOperator(To<CSSMathExpressionOperation>(node).OperatorType());
 }
 
 CSSNumericValue* NegateOrInvertIfRequired(CSSMathOperator parent_op,
@@ -130,21 +129,23 @@
     return CSSMathSum::Create(std::move(values));
   }
 
+  DCHECK(root.IsOperation());
+
   CSSNumericValueVector values;
 
   // When the node is a variadic operation, we return either a CSSMathMin or a
   // CSSMathMax.
-  if (root.IsVariadicOperation()) {
-    const auto& node = To<CSSMathExpressionVariadicOperation>(root);
+  if (const auto& node = To<CSSMathExpressionOperation>(root);
+      (node.IsMinOrMax())) {
     for (const auto& operand : node.GetOperands())
       values.push_back(CalcToNumericValue(*operand));
     if (node.OperatorType() == CSSMathOperator::kMin)
       return CSSMathMin::Create(std::move(values));
-    DCHECK(node.OperatorType() == CSSMathOperator::kMax);
-    return CSSMathMax::Create(std::move(values));
+    if (node.OperatorType() == CSSMathOperator::kMax)
+      return CSSMathMax::Create(std::move(values));
   }
 
-  DCHECK(root.IsBinaryOperation());
+  DCHECK_EQ(To<CSSMathExpressionOperation>(root).GetOperands().size(), 2u);
   // When the node is a binary operator, we return either a CSSMathSum or a
   // CSSMathProduct.
   // For cases like calc(1 + 2 + 3), the calc expression tree looks like:
@@ -165,19 +166,23 @@
   // the nodes that we encounter.
   const CSSMathExpressionNode* cur_node = &root;
   do {
-    DCHECK(cur_node->IsBinaryOperation());
-    const CSSMathExpressionBinaryOperation* binary_op =
-        To<CSSMathExpressionBinaryOperation>(cur_node);
-    DCHECK(binary_op->LeftExpressionNode());
-    DCHECK(binary_op->RightExpressionNode());
+    DCHECK(cur_node->IsOperation());
+    const CSSMathExpressionOperation* binary_op =
+        To<CSSMathExpressionOperation>(cur_node);
+    CSSMathExpressionOperation::Operands operands = binary_op->GetOperands();
+    DCHECK_EQ(operands.size(), 2u);
+    const auto* left_node = operands[0].Get();
+    const auto* right_node = operands[1].Get();
+    DCHECK(left_node);
+    DCHECK(right_node);
 
-    auto* const value = CalcToNumericValue(*binary_op->RightExpressionNode());
+    auto* const value = CalcToNumericValue(*right_node);
 
     // If the current node is a '-' or '/', it's really just a '+' or '*' with
     // the right child negated or inverted, respectively.
     values.push_back(
         NegateOrInvertIfRequired(binary_op->OperatorType(), value));
-    cur_node = binary_op->LeftExpressionNode();
+    cur_node = left_node;
   } while (CanCombineNodes(root, *cur_node));
 
   DCHECK(cur_node);
@@ -187,7 +192,7 @@
   // the values.
   std::reverse(values.begin(), values.end());
   CSSMathOperator operator_type =
-      To<CSSMathExpressionBinaryOperation>(root).OperatorType();
+      To<CSSMathExpressionOperation>(root).OperatorType();
   if (operator_type == CSSMathOperator::kAdd ||
       operator_type == CSSMathOperator::kSubtract)
     return CSSMathSum::Create(std::move(values));
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 6fbd06f..e2738d6 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -659,30 +659,11 @@
                      CSSPrimitiveValue::ValueRange value_range)
       : source_range_(range), range_(range) {
     const CSSParserToken& token = range.Peek();
-    switch (token.FunctionId()) {
-      case CSSValueID::kCalc:
-      case CSSValueID::kWebkitCalc:
-        calc_value_ = CSSMathFunctionValue::Create(
-            CSSMathExpressionNode::ParseCalc(ConsumeFunction(range_)),
-            value_range);
-        break;
-      case CSSValueID::kMin:
-        calc_value_ = CSSMathFunctionValue::Create(
-            CSSMathExpressionNode::ParseMin(ConsumeFunction(range_)),
-            value_range);
-        break;
-      case CSSValueID::kMax:
-        calc_value_ = CSSMathFunctionValue::Create(
-            CSSMathExpressionNode::ParseMax(ConsumeFunction(range_)),
-            value_range);
-        break;
-      case CSSValueID::kClamp:
-        calc_value_ = CSSMathFunctionValue::Create(
-            CSSMathExpressionNode::ParseClamp(ConsumeFunction(range_)),
-            value_range);
-        break;
-      default:
-        break;
+    if (token.GetType() == kFunctionToken) {
+      calc_value_ = CSSMathFunctionValue::Create(
+          CSSMathExpressionNode::ParseMathFunction(token.FunctionId(),
+                                                   ConsumeFunction(range_)),
+          value_range);
     }
     if (calc_value_ && calc_value_->HasComparisons())
       context.Count(WebFeature::kCSSComparisonFunctions);
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 810b3ea..6b5cbdd7 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -5348,4 +5348,25 @@
   ASSERT_EQ(0U, element_count);
 }
 
+TEST_F(StyleEngineTest, CSSComparisonFunctionsUseCount) {
+  ClearUseCounter(WebFeature::kCSSComparisonFunctions);
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      div { width: calc(10px + 20%); }
+    </style>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSComparisonFunctions));
+  ClearUseCounter(WebFeature::kCSSComparisonFunctions);
+
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      div { width: calc(min(10px, 20%) + max(20px, 10%)); }
+    </style>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_TRUE(IsUseCounted(WebFeature::kCSSComparisonFunctions));
+  ClearUseCounter(WebFeature::kCSSComparisonFunctions);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/events/event_target_names.json5 b/third_party/blink/renderer/core/events/event_target_names.json5
index a81bd07..90e7e05 100644
--- a/third_party/blink/renderer/core/events/event_target_names.json5
+++ b/third_party/blink/renderer/core/events/event_target_names.json5
@@ -51,6 +51,5 @@
     "Worker",
     "XMLHttpRequest",
     "XMLHttpRequestUpload",
-    "WindowControlsOverlay",
   ],
 }
diff --git a/third_party/blink/renderer/core/frame/build.gni b/third_party/blink/renderer/core/frame/build.gni
index f4d59de..e5dd511c 100644
--- a/third_party/blink/renderer/core/frame/build.gni
+++ b/third_party/blink/renderer/core/frame/build.gni
@@ -223,10 +223,19 @@
   "web_local_frame_client.cc",
   "web_remote_frame_impl.h",
   "window_event_handlers.h",
-  "window_controls_overlay.cc",
-  "window_controls_overlay.h",
-  "window_controls_overlay_geometry_change_event.cc",
-  "window_controls_overlay_geometry_change_event.h",
   "window_or_worker_global_scope.cc",
   "window_or_worker_global_scope.h",
 ]
+
+# WindowControlsOverlay
+# This uses target_os rather than current_os (which is what is_android is set
+# from) for the case of generating the v8 context snapshot for android. When
+# generating the snapshot for android, blink is compiled with
+# current_os="linux" and target_os="android". Using target_os is necessary as
+# we need to compile in the same way as would happen when current_os="android".
+if (target_os != "android") {
+  blink_core_sources_frame += [
+    "window_controls_overlay_changed_delegate.cc",
+    "window_controls_overlay_changed_delegate.h",
+  ]
+}
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index d02022a6..b1050c8b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -133,7 +133,6 @@
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
 #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
-#include "third_party/blink/renderer/core/frame/window_controls_overlay.h"
 #include "third_party/blink/renderer/core/fullscreen/scoped_allow_fullscreen.h"
 #include "third_party/blink/renderer/core/html/html_frame_element_base.h"
 #include "third_party/blink/renderer/core/html/html_link_element.h"
@@ -215,6 +214,10 @@
 #include "ui/gfx/range/range.h"
 #endif
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h"
+#endif
+
 namespace blink {
 
 namespace {
@@ -401,6 +404,9 @@
   visitor->Trace(background_color_paint_image_generator_);
   visitor->Trace(box_shadow_paint_image_generator_);
   visitor->Trace(clip_path_paint_image_generator_);
+#if !BUILDFLAG(IS_ANDROID)
+  visitor->Trace(window_controls_overlay_changed_delegate_);
+#endif
   Frame::Trace(visitor);
   Supplementable<LocalFrame>::Trace(visitor);
 }
@@ -2679,6 +2685,7 @@
 }
 #endif
 
+#if !BUILDFLAG(IS_ANDROID)
 void LocalFrame::UpdateWindowControlsOverlay(
     const gfx::Rect& bounding_rect_in_dips) {
   if (!RuntimeEnabledFeatures::WebAppWindowControlsOverlayEnabled(
@@ -2737,17 +2744,18 @@
     }
   }
 
-  if (fire_event) {
-    auto* window_controls_overlay =
-        WindowControlsOverlay::FromIfExists(*DomWindow()->navigator());
-
-    if (window_controls_overlay) {
-      window_controls_overlay->WindowControlsOverlayChanged(
-          window_controls_overlay_rect_);
-    }
+  if (fire_event && window_controls_overlay_changed_delegate_) {
+    window_controls_overlay_changed_delegate_->WindowControlsOverlayChanged(
+        window_controls_overlay_rect_);
   }
 }
 
+void LocalFrame::RegisterWindowControlsOverlayChangedDelegate(
+    WindowControlsOverlayChangedDelegate* delegate) {
+  window_controls_overlay_changed_delegate_ = delegate;
+}
+#endif
+
 HitTestResult LocalFrame::HitTestResultForVisualViewportPos(
     const gfx::Point& pos_in_viewport) {
   gfx::Point root_frame_point(
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index ffd2aabd..0184ce5 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -142,6 +142,10 @@
 class WebURLLoaderFactory;
 struct BlinkTransferableMessage;
 
+#if !BUILDFLAG(IS_ANDROID)
+class WindowControlsOverlayChangedDelegate;
+#endif
+
 extern template class CORE_EXTERN_TEMPLATE_EXPORT Supplement<LocalFrame>;
 
 // A LocalFrame is a frame hosted inside this process.
@@ -646,7 +650,12 @@
 #if BUILDFLAG(IS_MAC)
   void GetCharacterIndexAtPoint(const gfx::Point& point);
 #endif
+
+#if !BUILDFLAG(IS_ANDROID)
   void UpdateWindowControlsOverlay(const gfx::Rect& bounding_rect_in_dips);
+  void RegisterWindowControlsOverlayChangedDelegate(
+      WindowControlsOverlayChangedDelegate*);
+#endif
 
   SystemClipboard* GetSystemClipboard();
 
@@ -684,6 +693,7 @@
 
   bool SwapIn();
 
+#if !BUILDFLAG(IS_ANDROID)
   // For PWAs with display_overrides, these getters are information about the
   // titlebar bounds sent over from the browser via UpdateWindowControlsOverlay
   // in LocalMainFrame that are needed to persist the lifetime of the frame.
@@ -693,6 +703,7 @@
   const gfx::Rect& GetWindowControlsOverlayRect() const {
     return window_controls_overlay_rect_;
   }
+#endif
 
   void LoadJavaScriptURL(const KURL& url);
 
@@ -926,8 +937,12 @@
 
   PaymentRequestToken payment_request_token_;
 
+#if !BUILDFLAG(IS_ANDROID)
   bool is_window_controls_overlay_visible_ = false;
   gfx::Rect window_controls_overlay_rect_;
+  WeakMember<WindowControlsOverlayChangedDelegate>
+      window_controls_overlay_changed_delegate_;
+#endif
 
   // The evidence for or against a frame being an ad frame. `absl::nullopt` if
   // not yet set or if the frame is a top-level frame. (Only subframes can be
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 9ffbdb8..0b5eb64 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
@@ -1521,8 +1521,10 @@
           visual_properties.browser_controls_params);
     }
 
+#if !BUILDFLAG(IS_ANDROID)
     LocalRootImpl()->GetFrame()->UpdateWindowControlsOverlay(
         visual_properties.window_controls_overlay_rect);
+#endif
 
   } else {
     // Widgets in a WebView's frame tree without a local main frame
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.cc b/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.cc
new file mode 100644
index 0000000..d5e779fa
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.cc
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h"
+
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+
+namespace blink {
+
+WindowControlsOverlayChangedDelegate::WindowControlsOverlayChangedDelegate(
+    LocalFrame* frame) {
+  if (frame)
+    frame->RegisterWindowControlsOverlayChangedDelegate(this);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h b/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h
new file mode 100644
index 0000000..ee0a337
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_CHANGED_DELEGATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_CHANGED_DELEGATE_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace blink {
+
+class LocalFrame;
+
+// Extension point for observing changes to Window Controls Overlay geometry
+// (e.g. the titlebar area); only supported on desktop platforms.
+class CORE_EXPORT WindowControlsOverlayChangedDelegate
+    : public GarbageCollectedMixin {
+ public:
+  // Notifies about a change to the Window Controls Overlay geometry.
+  // `titlebar_area_rect` is the updated available titlebar area in the viewport
+  // coordinate space.
+  virtual void WindowControlsOverlayChanged(
+      const gfx::Rect& titlebar_area_rect) = 0;
+
+ protected:
+  // If `LocalFrame` not null, `this` will be registered to observe window
+  // controls overlay geometry changes. Otherwise, no notifications will be
+  // dispatched.
+  explicit WindowControlsOverlayChangedDelegate(LocalFrame*);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_CHANGED_DELEGATE_H_
diff --git a/third_party/blink/renderer/core/inspector/inspector_issue_conversion.cc b/third_party/blink/renderer/core/inspector/inspector_issue_conversion.cc
index 287cea3..d4abd16 100644
--- a/third_party/blink/renderer/core/inspector/inspector_issue_conversion.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_issue_conversion.cc
@@ -60,6 +60,9 @@
       return "";
     case mojom::blink::InspectorIssueCode::kLowTextContrastIssue:
       return protocol::Audits::InspectorIssueCodeEnum::LowTextContrastIssue;
+    case mojom::blink::InspectorIssueCode::kFederatedAuthRequestIssue:
+      CHECK(false);
+      return "";
     case mojom::blink::InspectorIssueCode::kGenericIssue:
       NOTREACHED();
       return "";
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
index 57813d5..afd08f0 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
@@ -166,8 +166,7 @@
   const MinMaxSizes grid_min_max =
       NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
           table, column_constraints, undistributable_space, is_fixed_layout,
-          /* is_layout_pass */ true,
-          /* skip_collapsed_columns */ false);
+          /* is_layout_pass */ true);
 
   // Standard: "used width of the table".
   LayoutUnit used_table_inline_size = ComputeUsedInlineSizeForTableFragment(
@@ -594,8 +593,7 @@
   const MinMaxSizes grid_min_max =
       NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
           Node(), *column_constraints, undistributable_space, is_fixed_layout,
-          /* is_layout_pass */ false,
-          /* skip_collapsed_columns */ false);
+          /* is_layout_pass */ false);
 
   MinMaxSizes min_max{
       std::max(grid_min_max.min_size, caption_constraint.min_size),
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
index 17a5f44..0f796660 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
@@ -893,8 +893,7 @@
     const NGTableTypes::Columns& column_constraints,
     LayoutUnit undistributable_space,
     bool is_fixed_layout,
-    bool is_layout_pass,
-    bool skip_collapsed_columns) {
+    bool is_layout_pass) {
   MinMaxSizes min_max;
   // https://www.w3.org/TR/css-tables-3/#computing-the-table-width
   // Compute standard GRID_MIN/GRID_MAX. They are sum of column_constraints.
@@ -919,8 +918,6 @@
   LayoutUnit non_percent_max_size_sum;
   float percent_sum = 0;
   for (const NGTableTypes::Column& column : column_constraints.data) {
-    if (skip_collapsed_columns && column.is_collapsed)
-      continue;
     if (column.min_inline_size) {
       // In fixed layout, constrained cells minimum inline size is their
       // maximum.
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h
index 1213933..79b05f4 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h
@@ -33,8 +33,7 @@
       const NGTableTypes::Columns& column_constraints,
       LayoutUnit undistributable_space,
       bool is_fixed_layout,
-      bool is_layout_pass,
-      bool skip_collapsed_columns);
+      bool is_layout_pass);
 
   static void DistributeColspanCellsToColumns(
       const NGTableTypes::ColspanCells& colspan_cells,
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
index 944028de..987df25 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
@@ -194,7 +194,6 @@
   LayoutUnit undistributable_space;
   bool is_fixed_layout = false;
   bool is_layout_pass = true;
-  bool skip_collapsed_columns = false;
 
   // No percentages, just sums up min/max.
   column_constraints->data.push_back(MakeColumn(10, 100));
@@ -203,7 +202,7 @@
 
   MinMaxSizes minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
       node, *column_constraints, undistributable_space, is_fixed_layout,
-      is_layout_pass, skip_collapsed_columns);
+      is_layout_pass);
   EXPECT_EQ(minmax.min_size, LayoutUnit(60));
   EXPECT_EQ(minmax.max_size, LayoutUnit(600));
 
@@ -215,14 +214,14 @@
   column_constraints->data.push_back(MakeColumn(10, 10));
   minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
       node, *column_constraints, undistributable_space, is_fixed_layout,
-      is_layout_pass, skip_collapsed_columns);
+      is_layout_pass);
   EXPECT_EQ(minmax.min_size, LayoutUnit(30));
   EXPECT_EQ(minmax.max_size, LayoutUnit(990));
 
   is_layout_pass = false;
   minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
       node, *column_constraints, undistributable_space, is_fixed_layout,
-      is_layout_pass, skip_collapsed_columns);
+      is_layout_pass);
   EXPECT_EQ(minmax.min_size, LayoutUnit(30));
   EXPECT_EQ(minmax.max_size, LayoutUnit(119));
 
@@ -235,7 +234,7 @@
   column_constraints->data.push_back(MakeColumn(10, 800));
   minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax(
       node, *column_constraints, undistributable_space, is_fixed_layout,
-      is_layout_pass, skip_collapsed_columns);
+      is_layout_pass);
   EXPECT_EQ(minmax.min_size, LayoutUnit(30));
   EXPECT_EQ(minmax.max_size, LayoutUnit(1000));
 }
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
index c676c2d..9428302 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
@@ -156,10 +156,11 @@
   if (auto* image = DynamicTo<HTMLImageElement>(node);
       image && image->WillRespondToMouseClickEvents()) {
     return true;
+  } else if (auto* anchor = DynamicTo<HTMLAnchorElement>(node);
+             anchor && !anchor->Href().IsEmpty()) {
+    return true;
   }
-  return IsA<HTMLFormControlElement>(node) ||
-         (IsA<HTMLAnchorElement>(node) &&
-          !To<HTMLAnchorElement>(node)->Href().IsEmpty());
+  return IsA<HTMLFormControlElement>(node);
 }
 
 // Skip the whole subtree if the object is invisible. Some elements in subtree
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index a332d16b..a4baa48 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -174,6 +174,7 @@
     sub_modules += [
       "//third_party/blink/renderer/modules/direct_sockets",
       "//third_party/blink/renderer/modules/serial",
+      "//third_party/blink/renderer/modules/window_controls_overlay",
     ]
   }
 
diff --git a/third_party/blink/renderer/modules/event_target_modules_names.json5 b/third_party/blink/renderer/modules/event_target_modules_names.json5
index 2bfc220..a88cfd8c 100644
--- a/third_party/blink/renderer/modules/event_target_modules_names.json5
+++ b/third_party/blink/renderer/modules/event_target_modules_names.json5
@@ -85,5 +85,6 @@
     },
     "USB",
     "WakeLockSentinel",
+    "WindowControlsOverlay",
   ],
 }
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 7afb0ae2..a6edf3c 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -220,6 +220,15 @@
 
 }  // namespace
 
+MediaStreamTrack* MediaStreamTrack::Create(ExecutionContext* context) {
+  MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>(
+      "dummy", MediaStreamSource::StreamType::kTypeVideo, "dummy",
+      false /* remote */);
+  MediaStreamComponent* component =
+      MakeGarbageCollected<MediaStreamComponent>(source);
+  return MakeGarbageCollected<MediaStreamTrack>(context, component);
+}
+
 MediaStreamTrack::MediaStreamTrack(ExecutionContext* context,
                                    MediaStreamComponent* component)
     : MediaStreamTrack(context,
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 593f9ff..40a14cf 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
@@ -64,6 +64,9 @@
     virtual void TrackChangedState() = 0;
   };
 
+  // TODO(1288839): Implement to recreate MST after transfer
+  static MediaStreamTrack* Create(ExecutionContext* context);
+
   MediaStreamTrack(ExecutionContext*, MediaStreamComponent*);
   MediaStreamTrack(ExecutionContext*,
                    MediaStreamComponent*,
diff --git a/third_party/blink/renderer/modules/window_controls_overlay/BUILD.gn b/third_party/blink/renderer/modules/window_controls_overlay/BUILD.gn
new file mode 100644
index 0000000..7bbaf93
--- /dev/null
+++ b/third_party/blink/renderer/modules/window_controls_overlay/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/blink/renderer/modules/modules.gni")
+
+# The WindowControlsOverlay API will not be supported on Android. Assert here to
+# ensure it doesn't sneak back into the build.
+assert(!is_android)
+
+blink_modules_sources("window_controls_overlay") {
+  sources = [
+    "window_controls_overlay.cc",
+    "window_controls_overlay.h",
+    "window_controls_overlay_geometry_change_event.cc",
+    "window_controls_overlay_geometry_change_event.h",
+  ]
+}
diff --git a/third_party/blink/renderer/core/frame/navigator_window_controls_overlay.idl b/third_party/blink/renderer/modules/window_controls_overlay/navigator_window_controls_overlay.idl
similarity index 100%
rename from third_party/blink/renderer/core/frame/navigator_window_controls_overlay.idl
rename to third_party/blink/renderer/modules/window_controls_overlay/navigator_window_controls_overlay.idl
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay.cc b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.cc
similarity index 85%
rename from third_party/blink/renderer/core/frame/window_controls_overlay.cc
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.cc
index c7e7ca4..8682abd 100644
--- a/third_party/blink/renderer/core/frame/window_controls_overlay.cc
+++ b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.cc
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/core/frame/window_controls_overlay.h"
+#include "third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.h"
 
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/navigator.h"
-#include "third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.h"
+#include "third_party/blink/renderer/modules/event_target_modules.h"
+#include "third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -38,7 +39,10 @@
 }
 
 WindowControlsOverlay::WindowControlsOverlay(Navigator& navigator)
-    : Supplement<Navigator>(navigator) {}
+    : Supplement<Navigator>(navigator),
+      WindowControlsOverlayChangedDelegate(
+          navigator.DomWindow() ? navigator.DomWindow()->GetFrame() : nullptr) {
+}
 
 WindowControlsOverlay::~WindowControlsOverlay() = default;
 
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay.h b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.h
similarity index 73%
rename from third_party/blink/renderer/core/frame/window_controls_overlay.h
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.h
index 116d75e..4b59c50 100644
--- a/third_party/blink/renderer/core/frame/window_controls_overlay.h
+++ b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_H_
 
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
-#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
+#include "third_party/blink/renderer/core/frame/window_controls_overlay_changed_delegate.h"
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -17,9 +17,10 @@
 
 class Navigator;
 
-class CORE_EXPORT WindowControlsOverlay final
+class WindowControlsOverlay final
     : public EventTargetWithInlineData,
-      public Supplement<Navigator> {
+      public Supplement<Navigator>,
+      public WindowControlsOverlayChangedDelegate {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -44,11 +45,11 @@
   bool visible() const;
   DOMRect* getTitlebarAreaRect() const;
 
-  void WindowControlsOverlayChanged(const gfx::Rect& rect);
+  void WindowControlsOverlayChanged(const gfx::Rect&) final;
 
   void Trace(Visitor*) const override;
 };
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_H_
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay.idl b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.idl
similarity index 100%
rename from third_party/blink/renderer/core/frame/window_controls_overlay.idl
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay.idl
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.cc b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.cc
similarity index 86%
rename from third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.cc
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.cc
index 3eb3d0a..cee10a0d 100644
--- a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.cc
+++ b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.h"
+#include "third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.h"
 
-#include "third_party/blink/renderer/bindings/core/v8/v8_window_controls_overlay_geometry_change_event_init.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_window_controls_overlay_geometry_change_event_init.h"
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "ui/gfx/geometry/rect.h"
 
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.h b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.h
similarity index 72%
rename from third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.h
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.h
index 1f4a9c7..2a250cc 100644
--- a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.h
+++ b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
 
-#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -40,4 +40,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WINDOW_CONTROLS_OVERLAY_WINDOW_CONTROLS_OVERLAY_GEOMETRY_CHANGE_EVENT_H_
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.idl b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.idl
similarity index 100%
rename from third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event.idl
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event.idl
diff --git a/third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event_init.idl b/third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event_init.idl
similarity index 100%
rename from third_party/blink/renderer/core/frame/window_controls_overlay_geometry_change_event_init.idl
rename to third_party/blink/renderer/modules/window_controls_overlay/window_controls_overlay_geometry_change_event_init.idl
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 14099f59..0ae85e7 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -708,7 +708,7 @@
     },
     {
       name: "CSSMixBlendModePlusLighter",
-      status: "test"
+      status: "stable"
     },
     {
       name: "CSSModules",
@@ -1508,6 +1508,10 @@
       name: "MediaStreamTrackInWorker",
       status: "test",
     },
+    {
+      name: "MediaStreamTrackTransfer",
+      status: "test",
+    },
     // This is enabled by default on Windows only. The only part that's
     // "experimental" is the support on other platforms.
     {
diff --git a/third_party/blink/tools/blinkpy/common/config/builders.json b/third_party/blink/tools/blinkpy/common/config/builders.json
index 7c410eb7..9f2b2a6 100644
--- a/third_party/blink/tools/blinkpy/common/config/builders.json
+++ b/third_party/blink/tools/blinkpy/common/config/builders.json
@@ -185,7 +185,7 @@
         ],
         "is_try_builder": true
     },
-    "android-web-platform-pie-x86-fyi-rel": {
+    "android-chrome-pie-x86-wpt-fyi-rel": {
         "port_name": "android-android-pie",
         "master": "chromium.android.fyi",
         "specifiers": [
diff --git a/third_party/blink/tools/diff_wpt_results.py b/third_party/blink/tools/diff_wpt_results.py
index 50955da..b4a53bc 100755
--- a/third_party/blink/tools/diff_wpt_results.py
+++ b/third_party/blink/tools/diff_wpt_results.py
@@ -54,7 +54,7 @@
 PRODUCTS_TO_BUILDER_NAME = {
     'android_weblayer': 'android-weblayer-pie-x86-wpt-fyi-rel',
     'android_webview': 'android-webview-pie-x86-wpt-fyi-rel',
-    'chrome_android': 'android-web-platform-pie-x86-fyi-rel',
+    'chrome_android': 'android-chrome-pie-x86-wpt-fyi-rel',
     'chrome_linux': 'linux-wpt-fyi-rel',
     'content_shell': "Linux Tests"}
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 47b15b27..5b2a929 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3177,6 +3177,27 @@
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-flexbox/flexbox_flow-column-wrap.html [ Crash Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-5.html [ Failure ]
+crbug.com/626703 [ Linux ] external/wpt/css/css-text/hyphens/hyphens-vertical-001.html [ Failure ]
+crbug.com/626703 [ Win ] external/wpt/css/css-text/hyphens/hyphens-vertical-001.html [ Failure ]
+crbug.com/626703 [ Linux ] external/wpt/css/css-text/hyphens/hyphens-vertical-002.html [ Failure ]
+crbug.com/626703 [ Win ] external/wpt/css/css-text/hyphens/hyphens-vertical-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-text/hyphens/hyphens-vertical-003.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-text/hyphens/hyphens-vertical-004.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/svg/geometry/reftests/circle-002.svg [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/webaudio/the-audio-api/the-mediastreamaudiosourcenode-interface/mediastreamaudiosourcenode-routing.html [ Timeout ]
+crbug.com/626703 [ Linux ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Mac ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Win7 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Linux ] virtual/fenced-frame-shadow-dom/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Mac ] virtual/fenced-frame-shadow-dom/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Win7 ] virtual/fenced-frame-shadow-dom/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Timeout ]
+crbug.com/626703 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip Timeout ]
+crbug.com/626703 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip Timeout ]
+crbug.com/626703 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip Timeout ]
+crbug.com/626703 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
+crbug.com/626703 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] virtual/threaded/external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-5.html [ Failure ]
 crbug.com/626703 external/wpt/css/compositing/background-blending/background-blend-mode-plus-lighter.html [ Failure ]
 crbug.com/626703 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-plus-lighter-svg-basic.html [ Failure ]
 crbug.com/626703 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-plus-lighter-svg.html [ Failure ]
@@ -7546,7 +7567,7 @@
 crbug.com/1286944 virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/parse-input-arguments-011.https.html [ Failure Pass ]
 
 # Sheriff 2022-01-17
-crbug.com/1287928 [ Mac ] external/wpt/webrtc/RTCPeerConnection-perfect-negotiation-stress-glare.https.html [ Failure Pass ]
+crbug.com/1287928 external/wpt/webrtc/RTCPeerConnection-perfect-negotiation-stress-glare.https.html [ Skip ]
 crbug.com/1233938 http/tests/notifications/click-shared-worker.html [ Pass Timeout ]
 # Temporarily disable test to allow fixing of devtools path escaping
 crbug.com/1094436 http/tests/devtools/overrides/project-added-with-existing-files-bind.js [ Failure Skip Timeout ]
@@ -7556,7 +7577,8 @@
 crbug.com/1288136 [ Mac ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/registered-property-value-005.https.html [ Failure Pass ]
 
 # Sheriff 2022-01-18
-crbug.com/1287067 virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Failure Pass ]
+crbug.com/1287067 [ Fuchsia ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Failure Pass ]
+crbug.com/1287067 [ Win10.20h2 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/embedder-require-corp.https.html [ Failure Pass ]
 crbug.com/1288264 [ Win ] http/tests/fetch/serviceworker-proxied/thorough/redirect-loop-base-https-other-https.html [ Pass Skip Timeout ]
 crbug.com/1288264 [ Win ] http/tests/fetch/window/thorough/cors-preflight2-base-https-other-https.html [ Pass Skip Timeout ]
 crbug.com/1288264 [ Win ] http/tests/fetch/serviceworker/thorough/nocors-base-https-other-https.html [ Pass Skip Timeout ]
@@ -7593,10 +7615,13 @@
 # Sheriff 2022-01-26
 crbug.com/1290978 [ Win ] external/wpt/css/CSS2/normal-flow/crashtests/block-in-inline-ax-crash.html [ Crash Failure Pass Timeout ]
 crbug.com/1290978 [ Mac ] external/wpt/css/CSS2/normal-flow/crashtests/block-in-inline-ax-crash.html [ Crash Failure Pass Timeout ]
-crbug.com/1290978 [ Win7 ] http/tests/devtools/elements/accessibility/edit-aria-attributes.js [ Crash Failure Pass Timeout Skip ]
+crbug.com/1290978 [ Win7 ] http/tests/devtools/elements/accessibility/edit-aria-attributes.js [ Crash Failure Pass Skip Timeout ]
 crbug.com/1291246 [ Mac ] virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance.html [ Failure Pass ]
 crbug.com/1291246 [ Mac ] virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance.html [ Failure Pass ]
 crbug.com/1291274 [ Mac ] virtual/document-transition/wpt_internal/document-transition/transition-waits-for-animations.html [ Crash ]
 crbug.com/1291383 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-perfect-negotiation-stress-glare-linear.https.html [ Skip ]
 crbug.com/1291383 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-perfect-negotiation-stress-glare.https.html [ Skip ]
 crbug.com/1291383 virtual/shared_array_buffer_on_desktop/fast/peerconnection/RTCPeerConnection-addMultipleTransceivers.html [ Skip ]
+
+# Sheriff 2022-01-27 Flaky test
+crbug.com/1291488 virtual/shared_array_buffer_on_desktop/fast/peerconnection/RTCPeerConnection-addMultipleTracks.html [ Skip ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 4e19f863..c5337e4 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -790,6 +790,7 @@
   {
     "prefix": "third-party-storage-partitioning",
     "bases": [
+      "external/wpt/web-locks/partitioned-web-locks.tentative.https.html",
       "external/wpt/workers/shared-worker-partitioned.tentative.html",
       "external/wpt/service-workers/service-worker/partitioned-service-worker.tentative.https.html",
       "external/wpt/service-workers/service-worker/partitioned-service-worker-getRegistrations.tentative.https.html",
diff --git a/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html b/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html
index 52348c9c..e155d8d2 100644
--- a/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html
+++ b/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html
@@ -10,6 +10,7 @@
   animation-duration: 0.1s;
   animation-timing-function: linear;
   animation-name: anim;
+  animation-play-state:  paused;
 }
 @keyframes anim {
   from { left: 200px; }
@@ -47,6 +48,9 @@
   animation-iteration-count: 2.4;
   animation-direction: alternate-reverse;
 }
+.running {
+  animation-play-state:  running;
+}
 </style>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
@@ -65,8 +69,7 @@
 async_test(t => {
   function assertAnimatedValue(expected, actual, isStart, id) {
     var identifier = (isStart ? 'Start' : 'End') + ' of animation for element \'' + id + '\'';
-    var diff = Math.abs(expected - actual);
-    assert_less_than(diff, 5, identifier + ': Saw something close to ' + expected);
+    assert_equals(actual, expected, identifier);
   }
 
   function endTest() {
@@ -85,6 +88,7 @@
       var expectedValue = expectedValues[i].start;
       var realValue = parseFloat(window.getComputedStyle(el).left);
       assertAnimatedValue(expectedValue, realValue, true, expectedValues[i].id);
+      el.classList.add('running');
     }
     document.addEventListener("animationend", t.step_func(() => {
       if (++animsFinished == numAnims) {
diff --git a/third_party/blink/web_tests/animations/stability/marquee-element-move-and-toggle-visibility-crash.html b/third_party/blink/web_tests/animations/stability/marquee-element-move-and-toggle-visibility-crash.html
new file mode 100644
index 0000000..36e1eac
--- /dev/null
+++ b/third_party/blink/web_tests/animations/stability/marquee-element-move-and-toggle-visibility-crash.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Moving a marquee element and toggle the visibility state should not
+         crash</title>
+</head>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<body>
+  <marquee id="target">Don't crash me</marquee>
+  <div id="container" style = "display: none;"></div>
+</body>
+<script>
+  test(function() {
+    const target = document.getElementById('target');
+    const container = document.getElementById('container');
+    requestAnimationFrame(() => {
+      requestAnimationFrame(() => {
+        // Removing the element pauses the animation in Blink.
+        target.parentElement.removeChild(target);
+        requestAnimationFrame(() => {
+          // Reattaching the element resumes the animation regardless of
+          // visibility.
+          container.appendChild(target);
+          requestAnimationFrame(() => {
+             container.style = "display: block";
+          });
+        });
+      });
+    });
+  }, 'Check that toggling visibility of a moved marquee element does not crash');
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 758cd28..e5b97a9 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: 165e350edf37719726d3240af2935cdec43d1447
+Version: 4600dd75e59f1fee349913b2633a8795aacc6490
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index a94e1fa3..ab861bceb 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -367,6 +367,13 @@
        {}
       ]
      ],
+     "chrome-bug-1289999-crash.https.html": [
+      "fab89f06d95b76d3fc63a8d4d784ffb93f3624de",
+      [
+       null,
+       {}
+      ]
+     ],
      "flexbox": {
       "fixed-flex-item-inside-abs-flex-in-multicol-crash.html": [
        "29ba56748f9a81615977ded26f4299fa744475f0",
@@ -3196,6 +3203,13 @@
        null,
        {}
       ]
+     ],
+     "scroll-timeline-in-removed-iframe-crash.html": [
+      "07edbd83f1c791b1e71afde9919f9f69aa42f05d",
+      [
+       null,
+       {}
+      ]
      ]
     }
    },
@@ -155922,6 +155936,58 @@
         {}
        ]
       ],
+      "hyphens-vertical-001.html": [
+       "baececdb0a94588af996175e0f2253d932f727a1",
+       [
+        null,
+        [
+         [
+          "/css/css-text/hyphens/reference/hyphens-vertical-001-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "hyphens-vertical-002.html": [
+       "e449526344d25562769b472698126b00829ead05",
+       [
+        null,
+        [
+         [
+          "/css/css-text/hyphens/reference/hyphens-vertical-002-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "hyphens-vertical-003.html": [
+       "2b1c6271c2e201a5b3af9e933f2bd02ca548844e",
+       [
+        null,
+        [
+         [
+          "/css/css-text/hyphens/reference/hyphens-vertical-003-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "hyphens-vertical-004.html": [
+       "cee7718b9d8fdebb30f0db39dad95a5fb8bcefeb",
+       [
+        null,
+        [
+         [
+          "/css/css-text/hyphens/reference/hyphens-vertical-004-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "shy-styling-001.html": [
        "98348d92a6987607cd2d591fd385f9e303a4471a",
        [
@@ -228530,7 +228596,7 @@
      []
     ],
     "META.yml": [
-     "7df27e1f40b09a70c9dcee46e9b1e40bbf355595",
+     "dbfac71522fa59318abb52edc530dc850fe66936",
      []
     ],
     "OWNERS": [
@@ -228678,7 +228744,7 @@
      []
     ],
     "META.yml": [
-     "c3f69ba181e69906ebeb1fe42f1e4255f17c47c5",
+     "e173970b5d77af1fcff868fa026dc2787d7a3cc0",
      []
     ],
     "OWNERS": [
@@ -228823,10 +228889,6 @@
        "98b7dd0fb58b4142f90eca341356eb0efbd09d2f",
        []
       ],
-      "notify-top-early.html": [
-       "0dd796f609c0659bd489e10cb02ac4816a88960b",
-       []
-      ],
       "page-with-base-url-common.html": [
        "8d9fedcc2b40d2eaeff55727ffa476a6e7c0e479",
        []
@@ -228886,6 +228948,18 @@
        []
       ]
      }
+    },
+    "ordering": {
+     "README.md": [
+      "9ae6c7b19ff77cac170dd8234aa11ea8f37a1200",
+      []
+     ],
+     "resources": {
+      "notify-top-early.html": [
+       "0dd796f609c0659bd489e10cb02ac4816a88960b",
+       []
+      ]
+     }
     }
    },
    "appmanifest": {
@@ -229628,7 +229702,7 @@
      []
     ],
     "META.yml": [
-     "a069984ddb226b6f07cd5f1408e159634dfec7f4",
+     "77d19a828dcfe6f0f19d367b0b9c809d2b6b21c1",
      []
     ],
     "OWNERS": [
@@ -233049,14 +233123,6 @@
      "b8a1d0a6098492b615dfae8a1e5e44ade8a2f3db",
      []
     ],
-    "cookieListItem_attributes.https.any-expected.txt": [
-     "084f511984369c8f1ef7b06bf6a46f4cc037d930",
-     []
-    ],
-    "cookieListItem_attributes.https.any.serviceworker-expected.txt": [
-     "084f511984369c8f1ef7b06bf6a46f4cc037d930",
-     []
-    ],
     "resources": {
      "always_changing_sw.sub.js": [
       "9fdf99848fa50316e275cd6636a5755270a9bb1e",
@@ -233113,24 +233179,24 @@
       ],
       "path": {
        "one.html": [
-        "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+        "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
         []
        ],
        "three.html": [
-        "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+        "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
         []
        ],
        "two.html": [
-        "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+        "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
         []
        ]
       },
       "path-redirect-shared.js": [
-       "7144e651e11c643a5a45f82c1295ede85b7a7bae",
+       "777d0abd3381b821e9097a9fd7607d0613418d3b",
        []
       ],
       "path.html": [
-       "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+       "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
        []
       ],
       "path.html.headers": [
@@ -233139,16 +233205,16 @@
       ],
       "pathfakeout": {
        "one.html": [
-        "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+        "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
         []
        ]
       },
       "pathfakeout.html": [
-       "1b53e773a18b5f55c6a790cd33ace6508041dd81",
+       "5ff90b9f15ffe68f02efffc31e3fb4f10f6821b2",
        []
       ],
       "secure-non-secure-child.html": [
-       "443b0a17f74e9b4cbf4ae9e70232f9adf9f5b22d",
+       "e5d68b8d07e6f590861d7851149e24804f057122",
        []
       ]
      }
@@ -233205,7 +233271,7 @@
       []
      ],
      "cookie-test.js": [
-      "2e435ddc5b52f1448c1e776eb9eb956650bf1ebd",
+      "7c3b8619eeafac3885c69917e3cc5d0687d8f73e",
       []
      ],
      "cookie.py": [
@@ -233233,7 +233299,7 @@
       []
      ],
      "echo-cookie.html": [
-      "172020c5fb955a8371527829a3b5cd7848f1519f",
+      "ab78af8325969b3eb6627d4aa58bfaa44028f94a",
       []
      ],
      "echo-json.py": [
@@ -265248,6 +265314,22 @@
         "7b3f8206417ffeff05a522c07f1efad9176cde9b",
         []
        ],
+       "hyphens-vertical-001-ref.html": [
+        "5be7a840d893f7e18c1e01f20a7e3f1a96627968",
+        []
+       ],
+       "hyphens-vertical-002-ref.html": [
+        "cb240212bcae4adaf06fb6549a86f0be311923ca",
+        []
+       ],
+       "hyphens-vertical-003-ref.html": [
+        "6d3ea15845585523e66ce53fe71eed6bdfa125f1",
+        []
+       ],
+       "hyphens-vertical-004-ref.html": [
+        "63c751165f873060027c809cb3fc165b8974fa99",
+        []
+       ],
        "shy-styling-001-alt-ref.html": [
         "c86cb5002867c899f54a59d25c0a0572e7658cd7",
         []
@@ -270926,7 +271008,7 @@
      ],
      "resources": {
       "testhelper.js": [
-       "e4b0ddb8b42c2fb00c74681ec37b7219fbd3eb60",
+       "2792ee51febe3e8ea98f953568a7291a543b6b3f",
        []
       ]
      },
@@ -283492,6 +283574,14 @@
       "a69aab487239021088f944b0a8cee2dad4b3d111",
       []
      ],
+     "nested-worker.https.window-expected.txt": [
+      "8718ffde7bc70a2802ec04ecd5c1e3b42e0dca32",
+      []
+     ],
+     "nested-worker.window-expected.txt": [
+      "8718ffde7bc70a2802ec04ecd5c1e3b42e0dca32",
+      []
+     ],
      "resources": {
       "fetcher.html": [
        "000a5cc25bb72b334d41ff05e7b8f22691f48f30",
@@ -283526,13 +283616,17 @@
        []
       ],
       "support.sub.js": [
-       "9fcba9d8edeaffd71f393097e6859232de12e29d",
+       "98ae1f3457cd5a1e5cf431b8150542b1eb4322a0",
        []
       ],
       "worker-fetcher.html": [
        "bd155a532bd2d9dd2b5f96383cb8bde27a289856",
        []
       ],
+      "worker-fetcher.js": [
+       "aab49afe6f47c752884d407cfa48ef38e3b4a847",
+       []
+      ],
       "xhr-sender.html": [
        "b131fa41f9a8bf14c5c0a1aac8fcc2593bec6c42",
        []
@@ -285184,7 +285278,7 @@
      []
     ],
     "META.yml": [
-     "bf48f80f8083f0ca13f432d16a7b3b22b3435526",
+     "916bc8074f1c4321d850cec54abcb3f388959938",
      []
     ],
     "OWNERS": [
@@ -285356,7 +285450,7 @@
      []
     ],
     "META.yml": [
-     "6a48535e3c7416eb1d54b1a6e6837230ba498b39",
+     "8f553a86ca3bf19fe429f4a31f20d97ed8b1babd",
      []
     ],
     "OWNERS": [
@@ -289347,6 +289441,10 @@
        "377c7296a781adb8ce5792cda50a4ded28472339",
        []
       ],
+      "fenced-frame-bypass.tentative.https.window-expected.txt": [
+       "dc538e12907885d23b2fa5b3a6f9c6364b88fdc4",
+       []
+      ],
       "fenced-frame.tentative.https.window-expected.txt": [
        "dc538e12907885d23b2fa5b3a6f9c6364b88fdc4",
        []
@@ -289357,7 +289455,7 @@
       ],
       "resources": {
        "common.js": [
-        "1378f3e9a75ff7b0587887792e626cc92772f4e6",
+        "e83bd094670dd4a8541031c4a66d738045484dc1",
         []
        ],
        "serviceworker-partitioning-helper.js": [
@@ -290454,6 +290552,18 @@
       "a5f7b3fd0a08e60729192a62902a9e9f07805821",
       []
      ],
+     "render-blocking-mechanism": {
+      "support": {
+       "target-red.css": [
+        "a387acd4ecb17a0a0419fb5b3c90a1f0b2f1b0ea",
+        []
+       ],
+       "test-render-blocking.js": [
+        "3cd3b7d39045ff8c81ef599bdff46324c2dec5a9",
+        []
+       ]
+      }
+     },
      "resources": {
       "self-origin-subframe.html": [
        "8759362bdf006f09472ca105c02f37115d4f6a15",
@@ -301028,7 +301138,7 @@
      []
     ],
     "META.yml": [
-     "3dd015f3ecb7d95f6677031448823cf5028f546f",
+     "ecbf633fd5e887d1c90aa5afbe571b0b8af9156f",
      []
     ],
     "OWNERS": [
@@ -303562,7 +303672,7 @@
      []
     ],
     "META.yml": [
-     "d7d395ee8f2a942327858b189dd8a6b2a249c14e",
+     "9193c0d4590c36fe0c5efc3d7eeca8205c434ed5",
      []
     ],
     "Magnetometer-disabled-by-feature-policy.https-expected.txt": [
@@ -305066,7 +305176,7 @@
      []
     ],
     "dictionary-manual.https-expected.txt": [
-     "2b6107ccb8ba8253418492763f08bb7fc5c017d1",
+     "f99d27a7bc153a8e9a8e2cc6086f3bbfe61680e2",
      []
     ]
    },
@@ -305117,6 +305227,10 @@
      "0dbcc32d0b271f9772ee5664ba9f5fee35e95a41",
      []
     ],
+    "service-worker.js": [
+     "05a8b99ad8a95f347abb7190b753440629284594",
+     []
+    ],
     "shared-worker.js": [
      "61ff67bcff03099cbf93fbb594e9b188d85a9f11",
      []
@@ -306112,7 +306226,7 @@
      []
     ],
     "META.yml": [
-     "427385826fca7a770a816abc5d98cc324f647f68",
+     "b780d2c90cbd27ee3387d5207d8584f74f725b64",
      []
     ],
     "OWNERS": [
@@ -309735,6 +309849,10 @@
      "a256bb2dc766a40f21115049572246bf003499ba",
      []
     ],
+    "ping-rt-entries-expected.txt": [
+     "9655aed4059d9f60b88d16dac3b03c05a39f15af",
+     []
+    ],
     "resource-timing-level1.js": [
      "95b5cdfb1ed0ca2bbfd6b692ad565512218dd7a4",
      []
@@ -309776,6 +309894,10 @@
       "31f0e3ab417a69079f20748751b2d6be9a7d5552",
       []
      ],
+     "close.html": [
+      "02c275f37b66e8a0b434c6fa57b347994d523118",
+      []
+     ],
      "connection-reuse-test.js": [
       "453fbd34051067480139058d40541368bce2207c",
       []
@@ -310036,6 +310158,10 @@
     "test_resource_timing.js": [
      "598a727bf88e7f47556d509f6929a86b64b0506c",
      []
+    ],
+    "worklet-rt-entries-expected.txt": [
+     "6943681e3c437dd9a97ac11a7bf38d7cac44b68f",
+     []
     ]
    },
    "resources": {
@@ -316750,7 +316876,7 @@
      []
     ],
     "META.yml": [
-     "91cd8c9d8056e8512e049280b7c1a43a76b3074f",
+     "411450e0b142931544fbaac5669352201ee37328",
      []
     ]
    },
@@ -338078,7 +338204,7 @@
       ]
      ],
      "currentchange-app-history-back-forward-same-doc.html": [
-      "85a93a21bd0f4a708dc99b751c533342743410ce",
+      "9b1b777614b9d263ff7efcaa3f8e2fc8731840e9",
       [
        null,
        {}
@@ -338154,13 +338280,6 @@
        {}
       ]
      ],
-     "currentchange-dispose-ordering.html": [
-      "f9d23f7dd5da79afb95bd998c618685c225c9e6a",
-      [
-       null,
-       {}
-      ]
-     ],
      "currentchange-history-back-same-doc.html": [
       "7cfecb15b8ef9e4dc4e2cf678262e889b8d02550",
       [
@@ -338324,13 +338443,6 @@
        {}
       ]
      ],
-     "navigate-cross-document-event-order.html": [
-      "1c1758a002eb8ac5e8ba8945dc6163d32a1a70f4",
-      [
-       null,
-       {}
-      ]
-     ],
      "navigate-history-state-replace.html": [
       "b4b9559fb56cb782941210eefbaef379afdb7e5a",
       [
@@ -338373,13 +338485,6 @@
        {}
       ]
      ],
-     "navigate-same-document-event-order.html": [
-      "4be537fb7ae1c2223fa536b476da614f54d8e3e4",
-      [
-       null,
-       {}
-      ]
-     ],
      "navigate-same-document.html": [
       "bad420272c40b7e0ccf4df73e0c4cea38615debd",
       [
@@ -338408,13 +338513,6 @@
        {}
       ]
      ],
-     "navigate-transitionWhile-reject-event-order.html": [
-      "96e742766cccc8430c03f1446f15a2a3a98cf923",
-      [
-       null,
-       {}
-      ]
-     ],
      "reload-base-url.html": [
       "35b29b9d2fb032f97f7bd8ba607f928aec60571e",
       [
@@ -339437,41 +339535,6 @@
        {}
       ]
      ],
-     "navigateerror-ordering-cross-document.html": [
-      "ba1edceead52cb79b9bcb77c317dca5e2636d817",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigateerror-ordering-location-api-reentrant.html": [
-      "435690fcbc7ade816d52c91c80372a7449755916",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigateerror-ordering-location-api.html": [
-      "92b76ad5ff0a502b48dff59f49ad6547c3749cc7",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigateerror-ordering-transitionWhile-reentrant.html": [
-      "f5b4f84e8b6911dc38be308603254550d391b971",
-      [
-       null,
-       {}
-      ]
-     ],
-     "navigateerror-ordering-transitionWhile.html": [
-      "8f99d8a8a021e6eeae90c0f46e360d05129614ad",
-      [
-       null,
-       {}
-      ]
-     ],
      "navigatesuccess-cross-document.html": [
       "1d528c1f5f9154c1bc089315a0ae11d72b7d3820",
       [
@@ -339529,7 +339592,7 @@
       ]
      ],
      "transitionWhile-and-navigate.html": [
-      "66a43b25ec2a9f251e2183cd4373e99fa56a13c7",
+      "65e9d0492784368687dc53d4ae75716d279073fe",
       [
        null,
        {}
@@ -339620,6 +339683,106 @@
       ]
      ]
     },
+    "ordering": {
+     "back-same-document.html": [
+      "f4a2c382b8b9fd7d642cd943ade4fc69f1e5a668",
+      [
+       "app-history/ordering/back-same-document.html?",
+       {}
+      ],
+      [
+       "app-history/ordering/back-same-document.html?currentchange",
+       {}
+      ],
+      [
+       "app-history/ordering/back-same-document.html?transitionWhile",
+       {}
+      ],
+      [
+       "app-history/ordering/back-same-document.html?transitionWhile&currentChange",
+       {}
+      ]
+     ],
+     "currentchange-dispose-ordering.html": [
+      "f9d23f7dd5da79afb95bd998c618685c225c9e6a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-cross-document-event-order.html": [
+      "1c1758a002eb8ac5e8ba8945dc6163d32a1a70f4",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigate-same-document-transitionWhile-reject.html": [
+      "84e95eea75259541c0a943cccec5fd5161de8b0d",
+      [
+       "app-history/ordering/navigate-same-document-transitionWhile-reject.html?",
+       {}
+      ],
+      [
+       "app-history/ordering/navigate-same-document-transitionWhile-reject.html?currentchange",
+       {}
+      ]
+     ],
+     "navigate-same-document.html": [
+      "0d30eeddfb67da3c1d5efaef8a7b7af938db0c38",
+      [
+       "app-history/ordering/navigate-same-document.html?",
+       {}
+      ],
+      [
+       "app-history/ordering/navigate-same-document.html?currentchange",
+       {}
+      ],
+      [
+       "app-history/ordering/navigate-same-document.html?transitionWhile",
+       {}
+      ],
+      [
+       "app-history/ordering/navigate-same-document.html?transitionWhile&currentChange",
+       {}
+      ]
+     ],
+     "navigateerror-ordering-location-api-reentrant.html": [
+      "435690fcbc7ade816d52c91c80372a7449755916",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigateerror-ordering-location-api.html": [
+      "92b76ad5ff0a502b48dff59f49ad6547c3749cc7",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigateerror-ordering-navigate-cross-document.html": [
+      "ba1edceead52cb79b9bcb77c317dca5e2636d817",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigateerror-ordering-transitionWhile-reentrant.html": [
+      "f5b4f84e8b6911dc38be308603254550d391b971",
+      [
+       null,
+       {}
+      ]
+     ],
+     "navigateerror-ordering-transitionWhile.html": [
+      "8f99d8a8a021e6eeae90c0f46e360d05129614ad",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
     "per-entry-events": {
      "dispose-cross-document.html": [
       "22ea15bae9846d0075ef3c8092f62a732c727d2b",
@@ -354981,7 +355144,7 @@
      ]
     ],
     "cookieListItem_attributes.https.any.js": [
-     "e46d6b6caa594d722f7502d06c18c13513e97213",
+     "88ec4e98a28e4f638687a20aada6f45a665202f5",
      [
       "cookie-store/cookieListItem_attributes.https.any.html",
       {
@@ -379131,7 +379294,7 @@
         ]
        ],
        "get-shorthand.html": [
-        "0355e3995f7de61a1a650bcc1380e3ed2b73b4b6",
+        "6ce318d5995cd10c1fd15d3d3c24ae7863783364",
         [
          null,
          {}
@@ -385990,13 +386153,6 @@
        {}
       ]
      ],
-     "has-specificity.html": [
-      "186d35fdd97286d5dcd6e5d7fddc3bd2c541349e",
-      [
-       null,
-       {}
-      ]
-     ],
      "hover-002.html": [
       "02cee99bf6cd161eb95806854cb12e39504a359e",
       [
@@ -417263,6 +417419,42 @@
        }
       ]
      ],
+     "nested-worker.https.window.js": [
+      "3eeb435badb2d00404e0e214e1d42c4be2817f8a",
+      [
+       "fetch/private-network-access/nested-worker.https.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/support.sub.js"
+         ]
+        ]
+       }
+      ]
+     ],
+     "nested-worker.window.js": [
+      "6d246e1c76d6c5f603c3e29430ae0586fdb9f5ad",
+      [
+       "fetch/private-network-access/nested-worker.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/support.sub.js"
+         ]
+        ]
+       }
+      ]
+     ],
      "service-worker-fetch.https.window.js": [
       "edb20c04940e3fa76a2dd99e71785f97285c31c6",
       [
@@ -443544,6 +443736,41 @@
         }
        ]
       ],
+      "fenced-frame-bypass.tentative.https.window.js": [
+       "f2c3090750661536774e00794d25db5887b9cfcb",
+       [
+        "html/cross-origin-embedder-policy/anonymous-iframe/fenced-frame-bypass.tentative.https.window.html",
+        {
+         "script_metadata": [
+          [
+           "script",
+           "/common/get-host-info.sub.js"
+          ],
+          [
+           "script",
+           "/common/utils.js"
+          ],
+          [
+           "script",
+           "/common/dispatcher/dispatcher.js"
+          ],
+          [
+           "script",
+           "../credentialless/resources/common.js"
+          ],
+          [
+           "script",
+           "./resources/common.js"
+          ],
+          [
+           "timeout",
+           "long"
+          ]
+         ],
+         "timeout": "long"
+        }
+       ]
+      ],
       "fenced-frame.tentative.https.window.js": [
        "8f6bfd35d761a69725f88efe484a31555c5d5b8a",
        [
@@ -446805,6 +447032,22 @@
        }
       ]
      ],
+     "render-blocking-mechanism": {
+      "parser-inserted-style-element.tentative.html": [
+       "9a358aa4938e762b31b43fb38381a763c2222c33",
+       [
+        null,
+        {}
+       ]
+      ],
+      "parser-inserted-stylesheet-link.tentative.html": [
+       "0a771448fd2d4152619f9f420e2d5c30efcd9626",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "self-origin.any.js": [
       "c103a32144fb96b9b76b91fd440e3725e9a5838d",
       [
@@ -451736,6 +451979,41 @@
          {}
         ]
        ],
+       "canvas-descendants-focusability-001.html": [
+        "327c9f49d6aeffc2b3eef49cc38c8a00ebf15f95",
+        [
+         null,
+         {}
+        ]
+       ],
+       "canvas-descendants-focusability-002.html": [
+        "aa607365d56faa320ee74da362b54a1f326e5c29",
+        [
+         null,
+         {}
+        ]
+       ],
+       "canvas-descendants-focusability-003.tentative.html": [
+        "98d60cfe9898447d41625c34c3ff4bcd1cec3788",
+        [
+         null,
+         {}
+        ]
+       ],
+       "canvas-descendants-focusability-004.tentative.html": [
+        "5d8dfcd2f4272b2cfb7981ac0d2cc4c67adaa296",
+        [
+         null,
+         {}
+        ]
+       ],
+       "canvas-descendants-focusability-005.html": [
+        "f3bee6b06bc1273dea3d2d9de6645a0c2b5aa1b7",
+        [
+         null,
+         {}
+        ]
+       ],
        "context.arguments.missing.html": [
         "eb4d69aed09c3989ececac9929e46f8b15b9aed1",
         [
@@ -456686,7 +456964,7 @@
         ]
        ],
        "popup-light-dismiss.tentative.html": [
-        "e0012587fd217a935409b6c70af4db5c94a73332",
+        "2b37c2d64510efd1e9dcb5bb7c0d877fd450fe36",
         [
          null,
          {
@@ -466217,6 +466495,13 @@
       {}
      ]
     ],
+    "inert-canvas-fallback-content.tentative.html": [
+     "f22549b50377f17b3e03cb6c26c996122b7c8e2b",
+     [
+      null,
+      {}
+     ]
+    ],
     "inert-computed-style.html": [
      "a68eb7465128937801d5417cbe4ed2ca1bfc3757",
      [
@@ -471972,6 +472257,13 @@
       }
      ]
     ],
+    "MediaStreamTrackGenerator-in-service-worker.https.html": [
+     "389a30d0d940e12bda9f520886d2dbed68532bd0",
+     [
+      null,
+      {}
+     ]
+    ],
     "MediaStreamTrackGenerator-in-shared-worker.https.html": [
      "deecfccad16f5df61028b9fc65e0e65e5f825e64",
      [
@@ -472046,7 +472338,7 @@
      ]
     ],
     "MediaRecorder-destroy-script-execution.html": [
-     "6375004d36b2816c1a5e54c403d33e24fe386e1a",
+     "3ea5a4c952c5ae3715a39901c1c0410221ac4012",
      [
       null,
       {}
@@ -495206,8 +495498,15 @@
        {}
       ]
      ],
+     "input.html": [
+      "a46d416671c1c6a14f979846cf3afd460d0cb0c9",
+      [
+       null,
+       {}
+      ]
+     ],
      "link.html": [
-      "6213ed8b45d72aca175384ba2fd6e4b6edbd0848",
+      "c49576a8e650bc5525bf35c3c2ca07c6ec64009e",
       [
        null,
        {}
@@ -495254,6 +495553,13 @@
        null,
        {}
       ]
+     ],
+     "workers.html": [
+      "3a23ad71a31555849bb8bf0a0161315cdd806785",
+      [
+       null,
+       {}
+      ]
      ]
     },
     "initiator-type-for-script.html": [
@@ -495263,6 +495569,13 @@
       {}
      ]
     ],
+    "input-sequence-of-events.html": [
+     "446e24a0bca8a48cc0ef7e62ddf3a84017e8e6f5",
+     [
+      null,
+      {}
+     ]
+    ],
     "nested-context-navigations-embed.html": [
      "5c8a62a9ae281d7f4823e27ae7fe37032de8cb2e",
      [
@@ -495332,6 +495645,13 @@
       {}
      ]
     ],
+    "ping-rt-entries.html": [
+     "34dad10b9f28b6b3dc9724f571aa3db7a82ad32c",
+     [
+      null,
+      {}
+     ]
+    ],
     "redirects.html": [
      "ba69907a5f6e46a3e8a79041da17039d14d20feb",
      [
@@ -495425,6 +495745,13 @@
       {}
      ]
     ],
+    "script-rt-entries.html": [
+     "cdd72bd1e25199085c8178b618a00200c86a3de9",
+     [
+      null,
+      {}
+     ]
+    ],
     "sizes-cache.any.js": [
      "af70e5a6ded3e63d158fd929feacf0827b3cafc9",
      [
@@ -495636,6 +495963,13 @@
       {}
      ]
     ],
+    "worklet-rt-entries.html": [
+     "8ed280be1706b60ff2f8942a503ab7e79e08bd15",
+     [
+      null,
+      {}
+     ]
+    ],
     "xhr-resource-timing.html": [
      "6f8f3331861062aa96b533cd94d9add21ea7e382",
      [
@@ -506780,6 +507114,13 @@
        }
       ]
      ],
+     "cross-realm-crash.window.js": [
+      "5fc7ce37a5f6d2dbd90b5f898bcd7b198f0c50b0",
+      [
+       "streams/readable-streams/cross-realm-crash.window.html",
+       {}
+      ]
+     ],
      "default-reader.any.js": [
       "664853e28cfb9f432631ac444dab9836f869bdec",
       [
diff --git a/third_party/blink/web_tests/external/wpt/accelerometer/META.yml b/third_party/blink/web_tests/external/wpt/accelerometer/META.yml
index 7df27e1f..dbfac71 100644
--- a/third_party/blink/web_tests/external/wpt/accelerometer/META.yml
+++ b/third_party/blink/web_tests/external/wpt/accelerometer/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/accelerometer/
 suggested_reviewers:
-  - zqzhang
   - riju
   - Honry
   - rakuco
diff --git a/third_party/blink/web_tests/external/wpt/ambient-light/META.yml b/third_party/blink/web_tests/external/wpt/ambient-light/META.yml
index c3f69ba..e173970 100644
--- a/third_party/blink/web_tests/external/wpt/ambient-light/META.yml
+++ b/third_party/blink/web_tests/external/wpt/ambient-light/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/ambient-light/
 suggested_reviewers:
-  - zqzhang
   - riju
   - rakuco
   - Honry
diff --git a/third_party/blink/web_tests/external/wpt/battery-status/META.yml b/third_party/blink/web_tests/external/wpt/battery-status/META.yml
index a069984d..77d19a82 100644
--- a/third_party/blink/web_tests/external/wpt/battery-status/META.yml
+++ b/third_party/blink/web_tests/external/wpt/battery-status/META.yml
@@ -1,5 +1,4 @@
 spec: https://w3c.github.io/battery/
 suggested_reviewers:
   - anssiko
-  - zqzhang
   - Honry
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-001.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-001.html
new file mode 100644
index 0000000..baececd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text test: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="match" href="reference/hyphens-vertical-001-ref.html">
+<meta name="assert" content="Check rendering of soft-hyphen character in vertical writing mode">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&shy;ation
+</div>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-002.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-002.html
new file mode 100644
index 0000000..e449526
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-002.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text test: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="match" href="reference/hyphens-vertical-002-ref.html">
+<meta name="assert" content="Check rendering of soft-hyphen character in vertical writing mode">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        text-orientation: upright;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&shy;ation
+</div>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-003.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-003.html
new file mode 100644
index 0000000..2b1c6271
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-003.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text test: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="help" href="https://drafts.csswg.org/css-text-4/#hyphenate-character">
+<link rel="match" href="reference/hyphens-vertical-003-ref.html">
+<meta name="assert" content="Check rendering of soft-hyphen character in vertical writing mode">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+        hyphenate-character: "+=";
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&shy;ation
+</div>
+
+<div>
+    hyphen+=<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-004.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-004.html
new file mode 100644
index 0000000..cee7718
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-vertical-004.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text test: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="help" href="https://drafts.csswg.org/css-text-4/#hyphenate-character">
+<link rel="match" href="reference/hyphens-vertical-004-ref.html">
+<meta name="assert" content="Check rendering of soft-hyphen character in vertical writing mode">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        text-orientation: upright;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+        hyphenate-character: "+=";
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&shy;ation
+</div>
+
+<div>
+    hyphen+=<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-001-ref.html
new file mode 100644
index 0000000..5be7a840
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-001-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text reference: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-002-ref.html
new file mode 100644
index 0000000..cb24021
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-002-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text reference: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        text-orientation: upright;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
+
+<div>
+    hyphen&#x2010;<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-003-ref.html
new file mode 100644
index 0000000..6d3ea15
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-003-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text reference: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+        hyphenate-character: "+=";
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen+=<br>ation
+</div>
+
+<div>
+    hyphen+=<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-004-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-004-ref.html
new file mode 100644
index 0000000..63c7511
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-vertical-004-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text reference: soft hyphens in vertical writing mode</title>
+<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
+
+<style>
+    div {
+        font: 16px monospace;
+        writing-mode: vertical-rl;
+        text-orientation: upright;
+        border: 1px solid gray;
+        margin: 10px;
+        padding: 2px;
+        hyphens: manual;
+        width: 3em;
+        height: 9ch;
+        hyphenate-character: "+=";
+    }
+</style>
+
+<p>Test passes if the two boxes look the same:</p>
+
+<div>
+    hyphen+=<br>ation
+</div>
+
+<div>
+    hyphen+=<br>ation
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/generic-sensor/META.yml b/third_party/blink/web_tests/external/wpt/generic-sensor/META.yml
index bf48f80..916bc80 100644
--- a/third_party/blink/web_tests/external/wpt/generic-sensor/META.yml
+++ b/third_party/blink/web_tests/external/wpt/generic-sensor/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/sensors/
 suggested_reviewers:
-  - zqzhang
   - riju
   - rakuco
   - Honry
diff --git a/third_party/blink/web_tests/external/wpt/gyroscope/META.yml b/third_party/blink/web_tests/external/wpt/gyroscope/META.yml
index 6a48535..8f553a8 100644
--- a/third_party/blink/web_tests/external/wpt/gyroscope/META.yml
+++ b/third_party/blink/web_tests/external/wpt/gyroscope/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/gyroscope/
 suggested_reviewers:
-  - zqzhang
   - riju
   - Honry
   - rakuco
diff --git a/third_party/blink/web_tests/external/wpt/html-media-capture/META.yml b/third_party/blink/web_tests/external/wpt/html-media-capture/META.yml
index 3dd015f..ecbf633f 100644
--- a/third_party/blink/web_tests/external/wpt/html-media-capture/META.yml
+++ b/third_party/blink/web_tests/external/wpt/html-media-capture/META.yml
@@ -1,3 +1,3 @@
 spec: https://w3c.github.io/html-media-capture/
 suggested_reviewers:
-  - zqzhang
+  - anssiko
diff --git a/third_party/blink/web_tests/external/wpt/magnetometer/META.yml b/third_party/blink/web_tests/external/wpt/magnetometer/META.yml
index d7d395e..9193c0d 100644
--- a/third_party/blink/web_tests/external/wpt/magnetometer/META.yml
+++ b/third_party/blink/web_tests/external/wpt/magnetometer/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/magnetometer/
 suggested_reviewers:
-  - zqzhang
   - riju
   - Honry
   - rakuco
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-streams/MediaStreamTrack-transfer.https-expected.txt b/third_party/blink/web_tests/external/wpt/mediacapture-streams/MediaStreamTrack-transfer.https-expected.txt
deleted file mode 100644
index 52a0b41..0000000
--- a/third_party/blink/web_tests/external/wpt/mediacapture-streams/MediaStreamTrack-transfer.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL MediaStreamTrack transfer to Worker promise_test: Unhandled rejection with value: object "DataCloneError: Failed to execute 'postMessage' on 'Worker': Value at index 0 does not have a transferable type."
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/orientation-sensor/META.yml b/third_party/blink/web_tests/external/wpt/orientation-sensor/META.yml
index 4273858..b780d2c9 100644
--- a/third_party/blink/web_tests/external/wpt/orientation-sensor/META.yml
+++ b/third_party/blink/web_tests/external/wpt/orientation-sensor/META.yml
@@ -1,6 +1,5 @@
 spec: https://w3c.github.io/orientation-sensor/
 suggested_reviewers:
-  - zqzhang
   - riju
   - Honry
   - rakuco
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/input.html b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/input.html
new file mode 100644
index 0000000..a46d416
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/input.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator type: input</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+</head>
+<body>
+<input type="image" src="/resource-timing/resources/resource_timing_test0.png">
+<script>
+  initiator_type_test("resource_timing_test0.png", "input", "<input type=image>");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/link.html b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/link.html
index 6213ed8..c49576a 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/link.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/link.html
@@ -18,6 +18,7 @@
     href="/resource-timing/resources/resource_timing_test0.css?id=preload">
 <link rel="prerender" href="/resource-timing/resources/green.html?id=prerender">
 <link rel="manifest" href="/resource-timing/resources/manifest.json">
+<link rel="modulepreload" href="resources/empty.js?id=modulePreload">
 <script>
   initiator_type_test("nested.css", "link", "<link>");
 
@@ -30,6 +31,7 @@
   initiator_type_test("resource_timing_test0.css?id=preload", "link", "<link preload>");
   initiator_type_test("green.html?id=prerender", "link", "<link prerender>");
   initiator_type_test("manifest.json", "link", "<link manifest>");
+  initiator_type_test("resources/empty.js?id=modulePreload", "other", "module preload");
 </script>
 <ol>This content forces a font to get fetched</ol>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/workers.html b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/workers.html
new file mode 100644
index 0000000..3a23ad71
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/initiator-type/workers.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiatorType: worker resources</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+<script src="resources/initiator-type-test.js"></script>
+
+</head>
+
+<script>
+  const moduleWorkerURL = 'resources/empty.js?moduleWorker';
+  const workerURL = 'resources/empty.js?worker';
+  new Worker(moduleWorkerURL, {type: "module"});
+  new Worker(workerURL, {type: "classic"});
+  initiator_type_test(workerURL, "other", "classic worker");
+  initiator_type_test(moduleWorkerURL, "other", "module worker");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/input-sequence-of-events.html b/third_party/blink/web_tests/external/wpt/resource-timing/input-sequence-of-events.html
new file mode 100644
index 0000000..446e24a0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/input-sequence-of-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test the sequence of events when reporting input timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+  async_test(t => {
+    const input = document.createElement('input');
+    input.type = "image";
+    const absoluteURL = new URL('resources/blue.png', location.href).toString();
+    t.add_cleanup(() => input.remove());
+    input.addEventListener('load', t.step_func(() => {
+      assert_equals(performance.getEntriesByName(absoluteURL).length, 1);
+      t.done();
+    }));
+    input.src = absoluteURL;
+    document.body.appendChild(input);
+  }, "An image input element should receive its load event after the ResourceTiming entry is available");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries-expected.txt b/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries-expected.txt
new file mode 100644
index 0000000..9655aed4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Hyperlink auditing (<a ping>) should have a resource timing entry promise_test: Unhandled rejection with value: object "Error: observe_entry: timeout"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries.html b/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries.html
new file mode 100644
index 0000000..34dad10b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/ping-rt-entries.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry For hyperlink audit (ping)</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resource-timing/resources/observe-entry.js"></script>
+</head>
+<body>
+<script>
+    promise_test(async t => {
+        const link = document.createElement('a');
+        const delay = 500;
+        const ping = `/xhr/resources/delay.py?ms=${delay}`;
+        link.setAttribute('href', 'resources/close.html');
+        link.setAttribute('target', '_blank');
+        link.setAttribute('ping', ping);
+        link.innerText = 'Link';
+        document.body.appendChild(link);
+        link.click();
+        const entry = await observe_entry(ping);
+        assert_equals(entry.initiatorType, 'ping');
+        assert_greater_than(entry.duration, delay);
+    }, "Hyperlink auditing (<a ping>) should have a resource timing entry");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/close.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/close.html
new file mode 100644
index 0000000..02c275f3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/close.html
@@ -0,0 +1 @@
+<script>window.close()</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/script-rt-entries.html b/third_party/blink/web_tests/external/wpt/resource-timing/script-rt-entries.html
new file mode 100644
index 0000000..cdd72bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/script-rt-entries.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry Sequence of Events for Scripts</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+    async_test(t => {
+        const script = document.createElement("script");
+        const url = new URL('resources/empty.js', location.href).toString();
+        script.addEventListener('load', t.step_func(() => {
+            assert_equals(performance.getEntriesByName(url).length, 1);
+            t.done();
+        }));
+        script.src = url;
+        document.body.appendChild(script);
+        t.add_cleanup(() => script.remove());
+    }, "The RT entry for script should be available when the script 'load' event fires");
+
+    async_test(t => {
+        const script = document.createElement("script");
+        const url = new URL('resources/non-existent.js', location.href).toString();
+        script.addEventListener('error', t.step_func(() => {
+            assert_equals(performance.getEntriesByName(url).length, 1);
+            t.done();
+        }));
+        script.src = url;
+        document.body.appendChild(script);
+        t.add_cleanup(() => script.remove());
+    }, "The RT entry for a non-existent script should be available when the script 'error' event fires");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries-expected.txt b/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries-expected.txt
new file mode 100644
index 0000000..6943681
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Worklets should generate Resource Timing Entries promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'addModule')"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries.html b/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries.html
new file mode 100644
index 0000000..8ed280b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/worklet-rt-entries.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing Entry Sequence of Events for Worklets</title>
+<link rel="help" href="https://w3c.github.io/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+    promise_test(async() => {
+        const url = new URL("/worklets/resources/empty-worklet-script.js", location.href).toString();
+        await CSS.paintWorklet.addModule(url);
+        assert_equals(performance.getEntriesByName(url).length, 1);
+    }, "Worklets should generate Resource Timing Entries");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/vibration/META.yml b/third_party/blink/web_tests/external/wpt/vibration/META.yml
index 91cd8c9d..411450e 100644
--- a/third_party/blink/web_tests/external/wpt/vibration/META.yml
+++ b/third_party/blink/web_tests/external/wpt/vibration/META.yml
@@ -1,3 +1,3 @@
 spec: https://w3c.github.io/vibration/
 suggested_reviewers:
-  - zqzhang
+  - anssiko
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt
deleted file mode 100644
index 8c99926e..0000000
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/update-and-send-events-replacement-expected.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-This is a testharness.js-based test.
-PASS Removes an animation when another covers the same properties
-PASS Removes an animation after another animation finishes
-PASS Removes an animation after multiple other animations finish
-PASS Removes an animation after it finishes
-PASS Removes an animation after seeking another animation
-PASS Removes an animation after seeking it
-PASS Removes an animation after updating the fill mode of another animation
-PASS Removes an animation after updating its fill mode
-PASS Removes an animation after updating another animation's effect to one with different timing
-PASS Removes an animation after updating its effect to one with different timing
-PASS Removes an animation after updating another animation's timeline
-PASS Removes an animation after updating its timeline
-PASS Removes an animation after updating another animation's effect's properties
-PASS Removes an animation after updating its effect's properties
-PASS Removes an animation after updating another animation's effect to one with different properties
-PASS Removes an animation after updating its effect to one with different properties
-PASS Removes an animation when another animation uses a shorthand
-PASS Removes an animation that uses a shorthand
-PASS Removes an animation by another animation using logical properties
-PASS Removes an animation using logical properties
-PASS Removes an animation by another animation using logical properties after updating the context
-PASS Removes an animation after updating another animation's effect's target
-PASS Removes an animation after updating its effect's target
-PASS Removes an animation after updating another animation's effect to one with a different target
-PASS Removes an animation after updating its effect to one with a different target
-PASS Does NOT remove a CSS animation tied to markup
-FAIL Removes a CSS animation no longer tied to markup assert_equals: expected "removed" but got "active"
-PASS Does NOT remove a CSS transition tied to markup
-FAIL Removes a CSS transition no longer tied to markup assert_equals: expected "removed" but got "active"
-PASS Dispatches an event when removing
-PASS Does NOT dispatch a remove event twice
-PASS Does NOT remove an animation after making a redundant change to another animation's current time
-PASS Does NOT remove an animation after making a redundant change to its current time
-PASS Does NOT remove an animation after making a redundant change to another animation's timeline
-PASS Does NOT remove an animation after making a redundant change to its timeline
-PASS Does NOT remove an animation after making a redundant change to another animation's effect's properties
-PASS Does NOT remove an animation after making a redundant change to its effect's properties
-PASS Updates ALL timelines before checking for replacement
-PASS Dispatches remove events after finish events
-PASS Fires remove event before requestAnimationFrame
-PASS Queues all remove events before running them
-PASS Performs removal in deeply nested iframes
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt
new file mode 100644
index 0000000..225cd82b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL WebLocks of an iframe under a 3rd-party site are partitioned assert_equals: The 1p iframe failed to acquire the lock expected (undefined) undefined but got (boolean) true
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https.html b/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https.html
new file mode 100644
index 0000000..eb4e8d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-locks/partitioned-web-locks.tentative.https.html
@@ -0,0 +1,64 @@
+
+< !DOCTYPE html >
+<meta charset="utf-8"/>
+<title>Web Locks API: Partitioned WebLocks</title>
+
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="../credentialless/resources/common.js"></script>
+<script src="resources/helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+const { HTTPS_ORIGIN, HTTPS_NOTSAMESITE_ORIGIN } = get_host_info();
+// Map of lock_id => function that releases a lock.
+const held = new Map();
+let next_lock_id = 1;
+
+async function run_test(t) {
+  let target_url = HTTPS_ORIGIN + '/web-locks/resources/iframe.html';
+  target_url = new URL(
+    `./resources/partitioned-parent.html?target=${encodeURIComponent(target_url)}`,
+    HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);
+
+  navigator.locks.request('testLock', {mode: 'exclusive', ifAvailable: true},
+        lock => {
+          if (lock === null) {
+            assert_true(false)
+            return;
+          }
+          let lock_id = next_lock_id++;
+          let release;
+          const promise = new Promise(r => { release = r; });
+          held.set(lock_id, release);
+          return promise;
+        });
+
+  const w = window.open(target_url);
+  const result = await new Promise(resolve => window.onmessage = resolve);
+
+  // When 3rd party storage partitioning is enabled, the iFrame should be able
+  // to aquire a lock with the same name as one exclusively held by the opener
+  // of its top window, even when that opener has the same origin.
+  assert_equals(result.data.failed, undefined,
+      'The 1p iframe failed to acquire the lock');
+
+  t.add_cleanup(() => {
+    w.close()
+    for(let i = 1; i < next_lock_id; i++){
+      held.get(i)();
+    }
+  });
+}
+
+promise_test(t => {
+  return run_test(t);
+}, 'WebLocks of an iframe under a 3rd-party site are partitioned');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/web-locks/resources/partitioned-parent.html b/third_party/blink/web_tests/external/wpt/web-locks/resources/partitioned-parent.html
new file mode 100644
index 0000000..5dafce49
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-locks/resources/partitioned-parent.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="referrer" content="origin">
+<script>
+async function onLoad() {
+  self.addEventListener('message', evt => {
+    if (self.opener)
+      self.opener.postMessage(evt.data, '*');
+    else
+      self.top.postMessage(evt.data, '*');
+  }, { once: true });
+
+  const params = new URLSearchParams(self.location.search);
+  const frame = document.createElement('iframe');
+  frame.src = params.get('target');
+  document.body.appendChild(frame);
+
+  frame.addEventListener('load', function(){
+    frame.contentWindow.postMessage({op: 'request',
+        name: 'testLock', ifAvailable: true}, '*');
+  });
+}
+self.addEventListener('load', onLoad);
+</script>
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-default-value-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-default-value-expected.png
index 701a374..a3addc5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-default-value-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-default-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-gradient-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-gradient-image-expected.png
index fb37361..5d07654 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-gradient-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-gradient-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-color-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-color-expected.png
index fe96bac..9ab503d 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-color-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-color-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-image-expected.png
index 2cdea910..228c5d60 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-svg-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-svg-expected.png
index 5d5574517..3645797b 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-svg-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-multiple-background-layers-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-multiple-background-layers-expected.png
index 6e462b0..d261b79 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-multiple-background-layers-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-multiple-background-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-single-layer-no-blending-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
index 26de77179..53bd4dd 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-tiled-gradient-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-tiled-gradient-expected.png
index 5b2cb7d..9eb2088 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-tiled-gradient-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/background-blend-mode-tiled-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-expected.png
index 02d7f3c..c660ac5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-stacking-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-stacking-expected.png
index 6eef91e..762f1fa 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-stacking-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/effect-background-blend-mode-stacking-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-1-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-1-expected.png
index 57e7c10..2a4cf3a 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-2-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-2-expected.png
index 7ae21576..5e7dd22 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-3-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-3-expected.png
index 089ff7c..4cfeeac5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-3-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/css3/blending/mix-blend-mode-isolated-group-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-background-image-space-expected.png
index 2549378..35bcf31e 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-background-image-space-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-border-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-border-image-expected.png
index 14cd13b..ddf6c04 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-border-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-image-filter-all-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-image-filter-all-expected.png
index 6283ca4..bca87bc 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-image-filter-all-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-image-filter-all-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-mask-image-svg-expected.png
index ed88c12..8b69fc1b 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-mask-image-svg-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-mask-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-object-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-object-expected.png
index 46483da..c3e811c 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-object-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-svg-fill-text-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-svg-fill-text-expected.png
index ee8d50e..feaee0d 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/ycbcr-with-cmyk-color-profile-expected.png
index 5d040c7..5d56b945 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/ycbcr-with-cmyk-color-profile-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/ycbcr-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-border-radius-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-border-radius-expected.png
index d1db2ab..6045346 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-border-radius-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-border-radius-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-expected.png
index 04a3919..9ae0ecc 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
index 9042d25..3958a23e 100644
--- a/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-gl/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-default-value-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-default-value-expected.png
index 701a374..a3addc5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-default-value-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-default-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-gradient-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-gradient-image-expected.png
index fb37361..c2634a3 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-gradient-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-gradient-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-color-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-color-expected.png
index fe96bac..9ab503d 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-color-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-color-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-svg-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-svg-expected.png
index 5d5574517..a9b7fbf 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-svg-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-image-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-multiple-background-layers-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-multiple-background-layers-expected.png
index 6e462b0..d261b79 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-multiple-background-layers-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-multiple-background-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-single-layer-no-blending-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
index 26de77179..53bd4dd 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-single-layer-no-blending-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-tiled-gradient-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-tiled-gradient-expected.png
index 5b2cb7d..9eb2088 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-tiled-gradient-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/background-blend-mode-tiled-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-expected.png
index 02d7f3c..c660ac5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-stacking-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-stacking-expected.png
index 6eef91e..762f1fa 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-stacking-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/effect-background-blend-mode-stacking-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-1-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-1-expected.png
index 57e7c10..2a4cf3a 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-2-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-2-expected.png
index 7ae21576..5e7dd22 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-3-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-3-expected.png
index 089ff7c..4cfeeac5 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-3-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/css3/blending/mix-blend-mode-isolated-group-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-cover-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-cover-expected.png
index d0e95e9..f31f678 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-cover-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-repeat-expected.png
index 6efe026..29fa56d 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-repeat-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-border-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-border-image-expected.png
index 14cd13b..ddf6c04 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-border-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-group-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-group-expected.png
index 47123e2..8561c37 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-group-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-object-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-object-expected.png
index 46483da..0ce0793 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-object-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/rgb-png-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/rgb-png-with-cmyk-color-profile-expected.png
index e8709c5..ed95cd22 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/rgb-png-with-cmyk-color-profile-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/rgb-png-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/ycbcr-with-cmyk-color-profile-expected.png
index 5d040c7..f978852 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/ycbcr-with-cmyk-color-profile-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/ycbcr-with-cmyk-color-profile-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-border-radius-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-border-radius-expected.png
index d1db2ab..6045346 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-border-radius-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-border-radius-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-expected.png
index 04a3919..9ae0ecc 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
index 9042d25..a770804 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/images/yuv-decode-eligible/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/media/color-profile-video-poster-image-expected.png b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/media/color-profile-video-poster-image-expected.png
index 4545b78..149fc763 100644
--- a/third_party/blink/web_tests/flag-specific/skia-vulkan-native/media/color-profile-video-poster-image-expected.png
+++ b/third_party/blink/web_tests/flag-specific/skia-vulkan-native/media/color-profile-video-poster-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/css-pseudo/idlharness-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/css-pseudo/idlharness-expected.txt
deleted file mode 100644
index f76169b..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/css/css-pseudo/idlharness-expected.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-This is a testharness.js-based test.
-FAIL idl_test setup promise_test: Unhandled rejection with value: object "TypeError: window.getPseudoElements is not a function"
-PASS idl_test validation
-PASS Partial interface Element: original interface defined
-PASS Partial interface Element: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-FAIL CSSPseudoElement interface: existence and properties of interface object assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface object length assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface object name assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: attribute type assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: attribute element assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: attribute parent assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: operation pseudo(CSSOMString) assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement must be primary interface of beforeElements.item(0) assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL Stringification of beforeElements.item(0) assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "element" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "parent" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "pseudo(CSSOMString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: calling pseudo(CSSOMString) on beforeElements.item(0) with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL Element interface: operation pseudo(CSSOMString) assert_own_property: interface prototype object missing non-static operation expected property "pseudo" missing
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-expected.txt
deleted file mode 100644
index 314db574..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-expected.txt
+++ /dev/null
@@ -1,752 +0,0 @@
-This is a testharness.js-based test.
-Found 736 tests; 389 PASS, 347 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Loading data…
-PASS Parsing: <http://example	.
-org> against <http://example.org/foo/bar>
-PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
-PASS Parsing: <https://test:@test> against <about:blank>
-PASS Parsing: <https://:@test> against <about:blank>
-FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
-FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
-PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
-PASS Parsing: <	   :foo.com   
-> against <http://example.org/foo/bar>
-PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
-PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
-PASS Parsing: <lolscheme:x x#x x> against <about:blank>
-PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
-FAIL Parsing: <http://f: /c> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: /c" but got "http://f:%20/c"
-PASS Parsing: <http://f:
-/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
-FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: 21 / b ? d # e " but got "http://f:%2021%20/%20b%20?%20d%20#%20e"
-PASS Parsing: <> against <http://example.org/foo/bar>
-PASS Parsing: <  	> against <http://example.org/foo/bar>
-PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
-PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <:> against <http://example.org/foo/bar>
-PASS Parsing: <:a> against <http://example.org/foo/bar>
-PASS Parsing: <:/> against <http://example.org/foo/bar>
-PASS Parsing: <:\> against <http://example.org/foo/bar>
-PASS Parsing: <:#> against <http://example.org/foo/bar>
-PASS Parsing: <#> against <http://example.org/foo/bar>
-PASS Parsing: <#/> against <http://example.org/foo/bar>
-PASS Parsing: <#\> against <http://example.org/foo/bar>
-PASS Parsing: <#;?> against <http://example.org/foo/bar>
-PASS Parsing: <?> against <http://example.org/foo/bar>
-PASS Parsing: </> against <http://example.org/foo/bar>
-PASS Parsing: <:23> against <http://example.org/foo/bar>
-PASS Parsing: </:23> against <http://example.org/foo/bar>
-PASS Parsing: <\x> against <http://example.org/foo/bar>
-PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
-PASS Parsing: <::> against <http://example.org/foo/bar>
-PASS Parsing: <::23> against <http://example.org/foo/bar>
-FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
-PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
-PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
-PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
-PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
-PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <foo:/> against <http://example.org/foo/bar>
-PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
-FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
-FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
-FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
-PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
-PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
-PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
-PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
-FAIL Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://[1::2]:3:4" but got "http://[1::2]:3:4/"
-FAIL Parsing: <http://2001::1> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1" but got "http://2001::1/"
-FAIL Parsing: <http://2001::1]> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]" but got "http://2001::1]/"
-FAIL Parsing: <http://2001::1]:80> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]:80" but got "http://2001::1]/"
-PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
-PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <file://example:1/> against <about:blank>
-PASS Parsing: <file://example:test/> against <about:blank>
-FAIL Parsing: <file://example%/> against <about:blank> assert_equals: failure should set href to input expected "file://example%/" but got "file://example%25/"
-PASS Parsing: <file://[example]/> against <about:blank>
-PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
-PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
-PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
-PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
-PASS Parsing: <#β> against <http://example.org/foo/bar>
-PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
-PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
-FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
-FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
-FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
-FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
-FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
-FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
-PASS Parsing: <//server/file> against <file:///tmp/mock/path>
-PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
-PASS Parsing: </\server/file> against <file:///tmp/mock/path>
-PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
-PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
-PASS Parsing: <//> against <file:///tmp/mock/path>
-PASS Parsing: <///> against <file:///tmp/mock/path>
-PASS Parsing: <///test> against <file:///tmp/mock/path>
-PASS Parsing: <file://test> against <file:///tmp/mock/path>
-FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
-FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
-FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
-PASS Parsing: <test> against <file:///tmp/mock/path>
-PASS Parsing: <file:test> against <file:///tmp/mock/path>
-PASS Parsing: <http://example.com/././foo> against <about:blank>
-PASS Parsing: <http://example.com/./.foo> against <about:blank>
-PASS Parsing: <http://example.com/foo/.> against <about:blank>
-PASS Parsing: <http://example.com/foo/./> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
-PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
-PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
-PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
-FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
-FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
-PASS Parsing: <http://example.com////../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
-PASS Parsing: <http://example.com/foo> against <about:blank>
-PASS Parsing: <http://example.com/%20foo> against <about:blank>
-PASS Parsing: <http://example.com/foo%> against <about:blank>
-PASS Parsing: <http://example.com/foo%2> against <about:blank>
-PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
-PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
-FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
-PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
-FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> assert_equals: href expected "http://example.com/foo%00%51" but got "http://example.com/foo%00Q"
-PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
-PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
-PASS Parsing: <http://example.com/foo	bar> against <about:blank>
-PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
-PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
-PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
-PASS Parsing: <http://example.com/你好你好> against <about:blank>
-PASS Parsing: <http://example.com/‥/foo> against <about:blank>
-PASS Parsing: <http://example.com//foo> against <about:blank>
-PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
-PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
-PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
-PASS Parsing: <data:test# »> against <about:blank>
-PASS Parsing: <http://www.google.com> against <about:blank>
-PASS Parsing: <http://192.0x00A80001> against <about:blank>
-FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
-PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
-PASS Parsing: <http://user:pass@/> against <about:blank>
-PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
-PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
-PASS Parsing: <http://foo:80/> against <about:blank>
-PASS Parsing: <http://foo:81/> against <about:blank>
-FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
-PASS Parsing: <http://foo:-80/> against <about:blank>
-PASS Parsing: <https://foo:443/> against <about:blank>
-PASS Parsing: <https://foo:80/> against <about:blank>
-PASS Parsing: <ftp://foo:21/> against <about:blank>
-PASS Parsing: <ftp://foo:80/> against <about:blank>
-FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
-FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
-PASS Parsing: <ws://foo:80/> against <about:blank>
-PASS Parsing: <ws://foo:81/> against <about:blank>
-PASS Parsing: <ws://foo:443/> against <about:blank>
-PASS Parsing: <ws://foo:815/> against <about:blank>
-PASS Parsing: <wss://foo:80/> against <about:blank>
-PASS Parsing: <wss://foo:81/> against <about:blank>
-PASS Parsing: <wss://foo:443/> against <about:blank>
-PASS Parsing: <wss://foo:815/> against <about:blank>
-PASS Parsing: <http:/example.com/> against <about:blank>
-PASS Parsing: <ftp:/example.com/> against <about:blank>
-PASS Parsing: <https:/example.com/> against <about:blank>
-PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
-PASS Parsing: <file:/example.com/> against <about:blank>
-PASS Parsing: <ftps:/example.com/> against <about:blank>
-PASS Parsing: <gopher:/example.com/> against <about:blank>
-PASS Parsing: <ws:/example.com/> against <about:blank>
-PASS Parsing: <wss:/example.com/> against <about:blank>
-PASS Parsing: <data:/example.com/> against <about:blank>
-PASS Parsing: <javascript:/example.com/> against <about:blank>
-PASS Parsing: <mailto:/example.com/> against <about:blank>
-PASS Parsing: <http:example.com/> against <about:blank>
-PASS Parsing: <ftp:example.com/> against <about:blank>
-PASS Parsing: <https:example.com/> against <about:blank>
-PASS Parsing: <madeupscheme:example.com/> against <about:blank>
-PASS Parsing: <ftps:example.com/> against <about:blank>
-PASS Parsing: <gopher:example.com/> against <about:blank>
-PASS Parsing: <ws:example.com/> against <about:blank>
-PASS Parsing: <wss:example.com/> against <about:blank>
-PASS Parsing: <data:example.com/> against <about:blank>
-PASS Parsing: <javascript:example.com/> against <about:blank>
-PASS Parsing: <mailto:example.com/> against <about:blank>
-PASS Parsing: <http:@www.example.com> against <about:blank>
-PASS Parsing: <http:/@www.example.com> against <about:blank>
-PASS Parsing: <http://@www.example.com> against <about:blank>
-PASS Parsing: <http:a:b@www.example.com> against <about:blank>
-PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
-PASS Parsing: <http://a:b@www.example.com> against <about:blank>
-PASS Parsing: <http://@pple.com> against <about:blank>
-PASS Parsing: <http::b@www.example.com> against <about:blank>
-PASS Parsing: <http:/:b@www.example.com> against <about:blank>
-PASS Parsing: <http://:b@www.example.com> against <about:blank>
-FAIL Parsing: <http:/:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/:@/www.example.com" but got "http:///www.example.com"
-PASS Parsing: <http://user@/www.example.com> against <about:blank>
-FAIL Parsing: <http:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <http:/@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <http://@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <https:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "https:@/www.example.com" but got "https:///www.example.com"
-FAIL Parsing: <http:a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:a:b@/www.example.com" but got "http://a:b@/www.example.com"
-FAIL Parsing: <http:/a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/a:b@/www.example.com" but got "http://a:b@/www.example.com"
-PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
-FAIL Parsing: <http::@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http::@/www.example.com" but got "http:///www.example.com"
-PASS Parsing: <http:a:@www.example.com> against <about:blank>
-PASS Parsing: <http:/a:@www.example.com> against <about:blank>
-PASS Parsing: <http://a:@www.example.com> against <about:blank>
-PASS Parsing: <http://www.@pple.com> against <about:blank>
-FAIL Parsing: <http:@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@:www.example.com" but got "http://:www.example.com/"
-FAIL Parsing: <http:/@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@:www.example.com" but got "http://:www.example.com/"
-FAIL Parsing: <http://@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@:www.example.com" but got "http://:www.example.com/"
-PASS Parsing: <http://:@www.example.com> against <about:blank>
-PASS Parsing: </> against <http://www.example.com/test>
-PASS Parsing: </test.txt> against <http://www.example.com/test>
-PASS Parsing: <.> against <http://www.example.com/test>
-PASS Parsing: <..> against <http://www.example.com/test>
-PASS Parsing: <test.txt> against <http://www.example.com/test>
-PASS Parsing: <./test.txt> against <http://www.example.com/test>
-PASS Parsing: <../test.txt> against <http://www.example.com/test>
-PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
-PASS Parsing: <../../test.txt> against <http://www.example.com/test>
-PASS Parsing: <中/test.txt> against <http://www.example.com/test>
-PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
-PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
-PASS Parsing: <file:...> against <http://www.example.com/test>
-PASS Parsing: <file:..> against <http://www.example.com/test>
-PASS Parsing: <file:a> against <http://www.example.com/test>
-PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
-FAIL Parsing: <http://example example.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[]" but got "http://[]/"
-FAIL Parsing: <http://[:]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[:]" but got "http://[:]/"
-FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
-PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
-PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
-FAIL Parsing: <http://﷐zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://﷐zyx.com" but got "http://%EF%BF%BDzyx.com/"
-FAIL Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%b7%90zyx.com" but got "http://%EF%BF%BDzyx.com/"
-FAIL Parsing: <https://�> against <about:blank> assert_equals: failure should set href to input expected "https://\ufffd" but got "https://%EF%BF%BD/"
-FAIL Parsing: <https://%EF%BF%BD> against <about:blank> assert_equals: failure should set href to input expected "https://%EF%BF%BD" but got "https://%EF%BF%BD/"
-PASS Parsing: <https://x/�?�#�> against <about:blank>
-FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://Go.com> against <http://other.com/>
-FAIL Parsing: <http://%41.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
-FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
-PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
-FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
-FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
-FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
-FAIL Parsing: <http://hello%00> against <http://other.com/> assert_equals: failure should set href to input expected "http://hello%00" but got "http://hello%00/"
-PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
-PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
-FAIL Parsing: <http://192.168.0.257> against <http://other.com/> assert_equals: failure should set href to input expected "http://192.168.0.257" but got "http://192.168.0.257/"
-FAIL Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/> assert_equals: failure should set href to input expected "http://%3g%78%63%30%2e%30%32%35%30%2E.01" but got "http://%253gxc0.0250..01/"
-FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <https://x x:12> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
-PASS Parsing: <http://./> against <about:blank>
-PASS Parsing: <http://../> against <about:blank>
-PASS Parsing: <http://[www.google.com]/> against <about:blank>
-FAIL Parsing: <http://[google.com]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[google.com]" but got "http://[google.com]/"
-FAIL Parsing: <http://[::1.2.3.4x]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[::1.2.3.4x]" but got "http://[::1.2.3.4x]/"
-FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
-PASS Parsing: <#> against <test:test>
-PASS Parsing: <#x> against <mailto:x@x.com>
-FAIL Parsing: <#x> against <data:,> assert_equals: href expected "data:,#x" but got "mailto:x@x.com#x"
-PASS Parsing: <#x> against <about:blank>
-PASS Parsing: <#> against <test:test?test>
-PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
-PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
-PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
-PASS Parsing: <http://host/?'> against <about:blank>
-FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
-PASS Parsing: </some/path> against <http://user@example.org/smth>
-PASS Parsing: <> against <http://user:pass@example.org:21/smth>
-PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
-FAIL Parsing: <i> against <sc:sd> assert_equals: failure should set href to input expected "i" but got ""
-FAIL Parsing: <i> against <sc:sd/sd> assert_equals: failure should set href to input expected "i" but got ""
-PASS Parsing: <i> against <sc:/pa/pa>
-FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
-FAIL Parsing: <../i> against <sc:sd> assert_equals: failure should set href to input expected "../i" but got ""
-FAIL Parsing: <../i> against <sc:sd/sd> assert_equals: failure should set href to input expected "../i" but got ""
-PASS Parsing: <../i> against <sc:/pa/pa>
-FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
-FAIL Parsing: </i> against <sc:sd> assert_equals: failure should set href to input expected "/i" but got ""
-FAIL Parsing: </i> against <sc:sd/sd> assert_equals: failure should set href to input expected "/i" but got ""
-PASS Parsing: </i> against <sc:/pa/pa>
-FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
-FAIL Parsing: <?i> against <sc:sd> assert_equals: failure should set href to input expected "?i" but got ""
-FAIL Parsing: <?i> against <sc:sd/sd> assert_equals: failure should set href to input expected "?i" but got ""
-PASS Parsing: <?i> against <sc:/pa/pa>
-FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
-PASS Parsing: <#i> against <sc:sd>
-PASS Parsing: <#i> against <sc:sd/sd>
-PASS Parsing: <#i> against <sc:/pa/pa>
-FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
-FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
-FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
-FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
-FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
-FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
-FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
-FAIL Parsing: <sc://@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://:/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://:12/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1/x" but got "sc://%C3%B1"
-PASS Parsing: <sc:\../> against <about:blank>
-PASS Parsing: <sc::a@example.net> against <about:blank>
-PASS Parsing: <wow:%NBD> against <about:blank>
-PASS Parsing: <wow:%1G> against <about:blank>
-FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
-FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: href expected "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF" but got "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD"
-FAIL Parsing: <sc://a\0b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a[b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a\b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a]b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a^b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
-FAIL Parsing: <foo://ho
-st/> against <about:blank> assert_equals: host expected "host" but got ""
-FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
-FAIL Parsing: <http://a\0b/> against <about:blank> assert_equals: failure should set href to input expected "http://a\0b/" but got "http://a%00b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x01b/" but got "http://a%01b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x02b/" but got "http://a%02b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x03b/" but got "http://a%03b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x04b/" but got "http://a%04b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x05b/" but got "http://a%05b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x06b/" but got "http://a%06b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x07b/" but got "http://a%07b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\bb/" but got "http://a%08b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\vb/" but got "http://a%0Bb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\fb/" but got "http://a%0Cb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x0eb/" but got "http://a%0Eb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x0fb/" but got "http://a%0Fb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x10b/" but got "http://a%10b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x11b/" but got "http://a%11b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x12b/" but got "http://a%12b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x13b/" but got "http://a%13b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x14b/" but got "http://a%14b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x15b/" but got "http://a%15b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x16b/" but got "http://a%16b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x17b/" but got "http://a%17b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x18b/" but got "http://a%18b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x19b/" but got "http://a%19b/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1ab/" but got "http://a%1Ab/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1bb/" but got "http://a%1Bb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1cb/" but got "http://a%1Cb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1db/" but got "http://a%1Db/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1eb/" but got "http://a%1Eb/"
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://a\x1fb/" but got "http://a%1Fb/"
-FAIL Parsing: <http://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a%b/> against <about:blank> assert_equals: failure should set href to input expected "http://a%b/" but got "http://a%25b/"
-FAIL Parsing: <http://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://a[b/> against <about:blank>
-PASS Parsing: <http://a]b/> against <about:blank>
-FAIL Parsing: <http://a^b> against <about:blank> assert_equals: failure should set href to input expected "http://a^b" but got "http://a%5Eb/"
-FAIL Parsing: <http://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ab/> against <about:blank> assert_equals: href expected "http://ab/" but got "http://a%7Fb/"
-PASS Parsing: <http://ho	st/> against <about:blank>
-PASS Parsing: <http://ho
-st/> against <about:blank>
-PASS Parsing: <http://ho\rst/> against <about:blank>
-PASS Parsing: <http://ho%00st/> against <about:blank>
-FAIL Parsing: <http://ho%01st/> against <about:blank> assert_equals: href expected "http://ho\x01st/" but got "http://ho%01st/"
-FAIL Parsing: <http://ho%02st/> against <about:blank> assert_equals: href expected "http://ho\x02st/" but got "http://ho%02st/"
-FAIL Parsing: <http://ho%03st/> against <about:blank> assert_equals: href expected "http://ho\x03st/" but got "http://ho%03st/"
-FAIL Parsing: <http://ho%04st/> against <about:blank> assert_equals: href expected "http://ho\x04st/" but got "http://ho%04st/"
-FAIL Parsing: <http://ho%05st/> against <about:blank> assert_equals: href expected "http://ho\x05st/" but got "http://ho%05st/"
-FAIL Parsing: <http://ho%06st/> against <about:blank> assert_equals: href expected "http://ho\x06st/" but got "http://ho%06st/"
-FAIL Parsing: <http://ho%07st/> against <about:blank> assert_equals: href expected "http://ho\x07st/" but got "http://ho%07st/"
-FAIL Parsing: <http://ho%08st/> against <about:blank> assert_equals: href expected "http://ho\bst/" but got "http://ho%08st/"
-PASS Parsing: <http://ho%09st/> against <about:blank>
-PASS Parsing: <http://ho%0Ast/> against <about:blank>
-FAIL Parsing: <http://ho%0Bst/> against <about:blank> assert_equals: href expected "http://ho\vst/" but got "http://ho%0Bst/"
-FAIL Parsing: <http://ho%0Cst/> against <about:blank> assert_equals: href expected "http://ho\fst/" but got "http://ho%0Cst/"
-PASS Parsing: <http://ho%0Dst/> against <about:blank>
-FAIL Parsing: <http://ho%0Est/> against <about:blank> assert_equals: href expected "http://ho\x0est/" but got "http://ho%0Est/"
-FAIL Parsing: <http://ho%0Fst/> against <about:blank> assert_equals: href expected "http://ho\x0fst/" but got "http://ho%0Fst/"
-FAIL Parsing: <http://ho%10st/> against <about:blank> assert_equals: href expected "http://ho\x10st/" but got "http://ho%10st/"
-FAIL Parsing: <http://ho%11st/> against <about:blank> assert_equals: href expected "http://ho\x11st/" but got "http://ho%11st/"
-FAIL Parsing: <http://ho%12st/> against <about:blank> assert_equals: href expected "http://ho\x12st/" but got "http://ho%12st/"
-FAIL Parsing: <http://ho%13st/> against <about:blank> assert_equals: href expected "http://ho\x13st/" but got "http://ho%13st/"
-FAIL Parsing: <http://ho%14st/> against <about:blank> assert_equals: href expected "http://ho\x14st/" but got "http://ho%14st/"
-FAIL Parsing: <http://ho%15st/> against <about:blank> assert_equals: href expected "http://ho\x15st/" but got "http://ho%15st/"
-FAIL Parsing: <http://ho%16st/> against <about:blank> assert_equals: href expected "http://ho\x16st/" but got "http://ho%16st/"
-FAIL Parsing: <http://ho%17st/> against <about:blank> assert_equals: href expected "http://ho\x17st/" but got "http://ho%17st/"
-FAIL Parsing: <http://ho%18st/> against <about:blank> assert_equals: href expected "http://ho\x18st/" but got "http://ho%18st/"
-FAIL Parsing: <http://ho%19st/> against <about:blank> assert_equals: href expected "http://ho\x19st/" but got "http://ho%19st/"
-FAIL Parsing: <http://ho%1Ast/> against <about:blank> assert_equals: href expected "http://ho\x1ast/" but got "http://ho%1Ast/"
-FAIL Parsing: <http://ho%1Bst/> against <about:blank> assert_equals: href expected "http://ho\x1bst/" but got "http://ho%1Bst/"
-FAIL Parsing: <http://ho%1Cst/> against <about:blank> assert_equals: href expected "http://ho\x1cst/" but got "http://ho%1Cst/"
-FAIL Parsing: <http://ho%1Dst/> against <about:blank> assert_equals: href expected "http://ho\x1dst/" but got "http://ho%1Dst/"
-FAIL Parsing: <http://ho%1Est/> against <about:blank> assert_equals: href expected "http://ho\x1est/" but got "http://ho%1Est/"
-FAIL Parsing: <http://ho%1Fst/> against <about:blank> assert_equals: href expected "http://ho\x1fst/" but got "http://ho%1Fst/"
-FAIL Parsing: <http://ho%20st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%23st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://ho%25st/> against <about:blank>
-PASS Parsing: <http://ho%2Fst/> against <about:blank>
-FAIL Parsing: <http://ho%3Ast/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%3Ast/" but got "http://ho:st/"
-FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://ho%3Fst/> against <about:blank>
-FAIL Parsing: <http://ho%40st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%5Bst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Bst/" but got "http://ho[st/"
-PASS Parsing: <http://ho%5Cst/> against <about:blank>
-FAIL Parsing: <http://ho%5Dst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Dst/" but got "http://ho]st/"
-FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%7Fst/> against <about:blank> assert_equals: href expected "http://host/" but got "http://ho%7Fst/"
-FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "http://\x01\x02\x03\x04\x05\x06\x07\b\v\f\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f!\"$&'()*+,-.;=_`{}~/" but got "http://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
-FAIL Parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~" but got ""
-FAIL Parsing: <ftp://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%80/" but got "ftp://example.com%EF%BF%BD/"
-FAIL Parsing: <ftp://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%A0/" but got "ftp://example.com%EF%BF%BD/"
-FAIL Parsing: <https://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%80/" but got "https://example.com%EF%BF%BD/"
-FAIL Parsing: <https://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%A0/" but got "https://example.com%EF%BF%BD/"
-PASS Parsing: <ftp://%e2%98%83> against <about:blank>
-PASS Parsing: <https://%e2%98%83> against <about:blank>
-PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
-PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
-PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
-PASS Parsing: <h	t
-t\rp://h	o
-s\rt:9	0
-0\r0/p	a
-t\rh?q	u
-e\rry#f	r
-a\rg> against <about:blank>
-PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing: <http:> against <http://example.org/foo/bar>
-PASS Parsing: <http:> against <https://example.org/foo/bar>
-PASS Parsing: <sc:> against <https://example.org/foo/bar>
-PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
-PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
-PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
-PASS Parsing: <http://192.168.257> against <http://other.com/>
-PASS Parsing: <http://192.168.257.> against <http://other.com/>
-PASS Parsing: <http://192.168.257.com> against <http://other.com/>
-PASS Parsing: <http://256> against <http://other.com/>
-PASS Parsing: <http://256.com> against <http://other.com/>
-PASS Parsing: <http://999999999> against <http://other.com/>
-PASS Parsing: <http://999999999.> against <http://other.com/>
-PASS Parsing: <http://999999999.com> against <http://other.com/>
-FAIL Parsing: <http://10000000000> against <http://other.com/> assert_equals: failure should set href to input expected "http://10000000000" but got "http://10000000000/"
-PASS Parsing: <http://10000000000.com> against <http://other.com/>
-PASS Parsing: <http://4294967295> against <http://other.com/>
-FAIL Parsing: <http://4294967296> against <http://other.com/> assert_equals: failure should set href to input expected "http://4294967296" but got "http://4294967296/"
-PASS Parsing: <http://0xffffffff> against <http://other.com/>
-FAIL Parsing: <http://0xffffffff1> against <http://other.com/> assert_equals: failure should set href to input expected "http://0xffffffff1" but got "http://0xffffffff1/"
-FAIL Parsing: <http://256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256" but got "http://256.256.256.256/"
-PASS Parsing: <https://0x.0x.0> against <about:blank>
-PASS Parsing: <https://0x100000000/test> against <about:blank>
-PASS Parsing: <https://256.0.0.1/test> against <about:blank>
-PASS Parsing: <file:///C%3A/> against <about:blank>
-PASS Parsing: <file:///C%7C/> against <about:blank>
-FAIL Parsing: <file://%43%3A> against <about:blank> assert_equals: failure should set href to input expected "file://%43%3A" but got "file://c:/"
-FAIL Parsing: <file://%43%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://%43|> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://C%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <https://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <asdf://%43|/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
-PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
-FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
-PASS Parsing: <..> against <file:///>
-FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
-FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
-PASS Parsing: </> against <file://h/a/b>
-FAIL Parsing: <//d:> against <file:///C:/a/b> assert_equals: href expected "file:///d:" but got "file://d:/"
-FAIL Parsing: <//d:/..> against <file:///C:/a/b> assert_equals: href expected "file:///d:/" but got "file://d:/"
-PASS Parsing: <..> against <file:///ab:/>
-PASS Parsing: <..> against <file:///1:/>
-PASS Parsing: <> against <file:///test?test#test>
-PASS Parsing: <file:> against <file:///test?test#test>
-PASS Parsing: <?x> against <file:///test?test#test>
-PASS Parsing: <file:?x> against <file:///test?test#test>
-PASS Parsing: <#x> against <file:///test?test#test>
-PASS Parsing: <file:#x> against <file:///test?test#test>
-FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
-FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
-FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
-FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
-PASS Parsing: <file://spider///> against <about:blank>
-FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
-PASS Parsing: <file:///localhost//cat> against <about:blank>
-FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
-FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
-FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
-PASS Parsing: <\//pig> against <file://lion/>
-FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
-FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
-PASS Parsing: </..//localhost//pig> against <file://lion/>
-PASS Parsing: <file://> against <file://ape/>
-PASS Parsing: </rooibos> against <file://tea/>
-PASS Parsing: </?chai> against <file://tea/>
-FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
-FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
-FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
-FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
-FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-FAIL Parsing: <C|
-/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-PASS Parsing: <C> against <file://host/dir/file>
-FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
-PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
-FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
-PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
-PASS Parsing: </c:/foo/bar> against <file://host/path>
-PASS Parsing: <file://example.net/C:/> against <about:blank>
-PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
-PASS Parsing: <file://[1::8]/C:/> against <about:blank>
-FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
-PASS Parsing: </C:/> against <file://host/>
-PASS Parsing: <file:C:/> against <file://host/>
-PASS Parsing: <file:/C:/> against <file://host/>
-FAIL Parsing: <//C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
-FAIL Parsing: <file://C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
-PASS Parsing: <///C:/> against <file://host/>
-PASS Parsing: <file:///C:/> against <file://host/>
-FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
-FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
-PASS Parsing: <file:> against <about:blank>
-PASS Parsing: <file:?q=v> against <about:blank>
-PASS Parsing: <file:#frag> against <about:blank>
-PASS Parsing: <file:///Y:> against <about:blank>
-PASS Parsing: <file:///Y:/> against <about:blank>
-PASS Parsing: <file:///./Y> against <about:blank>
-PASS Parsing: <file:///./Y:> against <about:blank>
-FAIL Parsing: <\\\.\Y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\Y:" but got ""
-PASS Parsing: <file:///y:> against <about:blank>
-PASS Parsing: <file:///y:/> against <about:blank>
-PASS Parsing: <file:///./y> against <about:blank>
-PASS Parsing: <file:///./y:> against <about:blank>
-FAIL Parsing: <\\\.\y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\y:" but got ""
-FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
-FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
-FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
-PASS Parsing: <file:///one/two> against <file:///>
-FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
-PASS Parsing: <//one/two> against <file:///>
-PASS Parsing: <///one/two> against <file:///>
-FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
-PASS Parsing: <file:///.//> against <file:////>
-PASS Parsing: <file:.//p> against <about:blank>
-PASS Parsing: <file:/.//p> against <about:blank>
-PASS Parsing: <http://[1:0::]> against <http://example.net/>
-FAIL Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/> assert_equals: failure should set href to input expected "http://[0:1:2:3:4:5:6:7:8]" but got "http://[0:1:2:3:4:5:6:7:8]/"
-FAIL Parsing: <https://[0::0::0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0::0::0]" but got "https://[0::0::0]/"
-FAIL Parsing: <https://[0:.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:.0]" but got "https://[0:.0]/"
-FAIL Parsing: <https://[0:0:]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:0:]" but got "https://[0:0:]/"
-FAIL Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1:2:3:4:5:6:7.0.0.0.1]" but got "https://[0:1:2:3:4:5:6:7.0.0.0.1]/"
-FAIL Parsing: <https://[0:1.00.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.00.0.0.0]" but got "https://[0:1.00.0.0.0]/"
-FAIL Parsing: <https://[0:1.290.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.290.0.0.0]" but got "https://[0:1.290.0.0.0]/"
-FAIL Parsing: <https://[0:1.23.23]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.23.23]" but got "https://[0:1.23.23]/"
-FAIL Parsing: <http://?> against <about:blank> assert_equals: failure should set href to input expected "http://?" but got "http:/?"
-FAIL Parsing: <http://#> against <about:blank> assert_equals: failure should set href to input expected "http://#" but got "http:/#"
-PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
-PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
-PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
-FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <#x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1#x" but got "sc://%C3%B1"
-FAIL Parsing: <?x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1?x" but got "sc://%C3%B1"
-FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
-FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
-FAIL Parsing: <///> against <sc://x/> assert_equals: href expected "sc:///" but got "sc:"
-FAIL Parsing: <////> against <sc://x/> assert_equals: href expected "sc:////" but got "sc:"
-FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
-FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
-FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
-FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
-FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
-FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
-FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
-FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
-FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
-FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
-FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
-PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
-PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
-FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
-FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
-FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
-FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
-FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
-FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
-FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
-FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
-FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
-FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
-FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
-FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
-FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
-FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
-FAIL Parsing: <non-special://[:80/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <blob:https://example.com:443/> against <about:blank>
-PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
-PASS Parsing: <blob:> against <about:blank>
-PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
-PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
-FAIL Parsing: <http://[::127.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "http://[::127.0.0.0.1]" but got "http://[::127.0.0.0.1]/"
-PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
-PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
-PASS Parsing: <http://example.org/test?"> against <about:blank>
-PASS Parsing: <http://example.org/test?#> against <about:blank>
-PASS Parsing: <http://example.org/test?<> against <about:blank>
-PASS Parsing: <http://example.org/test?>> against <about:blank>
-PASS Parsing: <http://example.org/test?⌣> against <about:blank>
-PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
-PASS Parsing: <http://example.org/test?%GH> against <about:blank>
-PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
-PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
-FAIL Parsing: <a> against <about:blank> assert_equals: failure should set href to input expected "a" but got ""
-FAIL Parsing: <a/> against <about:blank> assert_equals: failure should set href to input expected "a/" but got ""
-FAIL Parsing: <a//> against <about:blank> assert_equals: failure should set href to input expected "a//" but got ""
-FAIL Parsing: <test-a-colon.html> against <a:> assert_equals: failure should set href to input expected "test-a-colon.html" but got ""
-FAIL Parsing: <test-a-colon-b.html> against <a:b> assert_equals: failure should set href to input expected "test-a-colon-b.html" but got ""
-PASS Parsing: <test-a-colon-slash.html> against <a:/>
-FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> assert_equals: href expected "a:///test-a-colon-slash-slash.html" but got ""
-PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
-FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> assert_equals: href expected "a://b/test-a-colon-slash-slash-b.html" but got "a://b"
-PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
-FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
-PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
-PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
-PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
-PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
-PASS Parsing: <file://a­b/p> against <about:blank>
-PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
-FAIL Parsing: <file://­/p> against <about:blank> assert_equals: failure should set href to input expected "file://­/p" but got "file://%C2%AD/p"
-PASS Parsing: <file://%C2%AD/p> against <about:blank>
-FAIL Parsing: <file://xn--/p> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <#link> against <https://example.org/##link>
-PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
-PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
-PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
-FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
-FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
-FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
-FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
-FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
-FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "wss://!\"$&'()*+,-.;=_`{}~/" but got "wss://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
-FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
-FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
-FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
-PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
-PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
-FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
-PASS Parsing: <abc:rootless> against <abc:path>
-FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
-FAIL Parsing: <http://1.2.3.4.5> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5" but got "http://1.2.3.4.5/"
-FAIL Parsing: <http://1.2.3.4.5.> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5." but got "http://1.2.3.4.5./"
-PASS Parsing: <http://0..0x300/> against <about:blank>
-PASS Parsing: <http://0..0x300./> against <about:blank>
-FAIL Parsing: <http://256.256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256" but got "http://256.256.256.256.256/"
-FAIL Parsing: <http://256.256.256.256.256.> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256." but got "http://256.256.256.256.256./"
-FAIL Parsing: <http://1.2.3.08> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08" but got "http://1.2.3.08/"
-FAIL Parsing: <http://1.2.3.08.> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08." but got "http://1.2.3.08./"
-FAIL Parsing: <http://1.2.3.09> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.09" but got "http://1.2.3.09/"
-FAIL Parsing: <http://09.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4" but got "http://09.2.3.4/"
-FAIL Parsing: <http://09.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4." but got "http://09.2.3.4./"
-FAIL Parsing: <http://01.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5" but got "http://01.2.3.4.5/"
-FAIL Parsing: <http://01.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5." but got "http://01.2.3.4.5./"
-FAIL Parsing: <http://0x100.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4" but got "http://0x100.2.3.4/"
-FAIL Parsing: <http://0x100.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4." but got "http://0x100.2.3.4./"
-FAIL Parsing: <http://0x1.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5" but got "http://0x1.2.3.4.5/"
-FAIL Parsing: <http://0x1.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5." but got "http://0x1.2.3.4.5./"
-FAIL Parsing: <http://foo.1.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4" but got "http://foo.1.2.3.4/"
-FAIL Parsing: <http://foo.1.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4." but got "http://foo.1.2.3.4./"
-FAIL Parsing: <http://foo.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4" but got "http://foo.2.3.4/"
-FAIL Parsing: <http://foo.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4." but got "http://foo.2.3.4./"
-FAIL Parsing: <http://foo.09> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09" but got "http://foo.09/"
-FAIL Parsing: <http://foo.09.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09." but got "http://foo.09./"
-FAIL Parsing: <http://foo.0x4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4" but got "http://foo.0x4/"
-FAIL Parsing: <http://foo.0x4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4." but got "http://foo.0x4./"
-PASS Parsing: <http://foo.09..> against <about:blank>
-PASS Parsing: <http://0999999999999999999/> against <about:blank>
-FAIL Parsing: <http://foo.0x> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x" but got "http://foo.0x/"
-FAIL Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123" but got "http://foo.0xfffffffffffffffffface123/"
-FAIL Parsing: <http://💩.123/> against <about:blank> assert_equals: failure should set href to input expected "http://💩.123/" but got "http://xn--ls8h.123/"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-origin-expected.txt
deleted file mode 100644
index 89bc28a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.14/external/wpt/url/a-element-origin-expected.txt
+++ /dev/null
@@ -1,342 +0,0 @@
-This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Loading data…
-PASS Parsing origin: <http://example	.
-org> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
-PASS Parsing origin: <https://test:@test> against <about:blank>
-PASS Parsing origin: <https://:@test> against <about:blank>
-PASS Parsing origin: <non-special://test:@test/x> against <about:blank>
-PASS Parsing origin: <non-special://:@test/x> against <about:blank>
-PASS Parsing origin: <http:foo.com> against <http://example.org/foo/bar>
-PASS Parsing origin: <	   :foo.com   
-> against <http://example.org/foo/bar>
-PASS Parsing origin: < foo.com  > against <http://example.org/foo/bar>
-PASS Parsing origin: <a:	 foo.com> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:0/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:00000000000000/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:
-/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <> against <http://example.org/foo/bar>
-PASS Parsing origin: <  	> against <http://example.org/foo/bar>
-PASS Parsing origin: <:foo.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <:foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <:> against <http://example.org/foo/bar>
-PASS Parsing origin: <:a> against <http://example.org/foo/bar>
-PASS Parsing origin: <:/> against <http://example.org/foo/bar>
-PASS Parsing origin: <:\> against <http://example.org/foo/bar>
-PASS Parsing origin: <:#> against <http://example.org/foo/bar>
-PASS Parsing origin: <#> against <http://example.org/foo/bar>
-PASS Parsing origin: <#/> against <http://example.org/foo/bar>
-PASS Parsing origin: <#\> against <http://example.org/foo/bar>
-PASS Parsing origin: <#;?> against <http://example.org/foo/bar>
-PASS Parsing origin: <?> against <http://example.org/foo/bar>
-PASS Parsing origin: </> against <http://example.org/foo/bar>
-PASS Parsing origin: <:23> against <http://example.org/foo/bar>
-PASS Parsing origin: </:23> against <http://example.org/foo/bar>
-PASS Parsing origin: <\x> against <http://example.org/foo/bar>
-PASS Parsing origin: <\\x\hello> against <http://example.org/foo/bar>
-PASS Parsing origin: <::> against <http://example.org/foo/bar>
-PASS Parsing origin: <::23> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://a:b@c:29/d> against <http://example.org/foo/bar>
-PASS Parsing origin: <http::@c:29> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://::@c@d:2> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo.com:b@d/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo.com/\@> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:\\foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:/bar.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://///////> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://///////bar.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:////://///> against <http://example.org/foo/bar>
-PASS Parsing origin: <c:/foo> against <http://example.org/foo/bar>
-PASS Parsing origin: <//foo/bar> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
-PASS Parsing origin: <[61:24:74]:98> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:[61:27]/:foo> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[2001::1]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[2001::1]:80> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftp:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <https:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftps:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <gopher:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ws:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <wss:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <javascript:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <mailto:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftp:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <https:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <madeupscheme:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftps:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <gopher:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ws:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <wss:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <javascript:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <mailto:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/b/c> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/ /c> against <http://example.org/foo/bar>
-PASS Parsing origin: </a%2fc> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/%2f/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <#β> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:text/html,test#test> against <http://example.org/foo/bar>
-PASS Parsing origin: <tel:1234567890> against <http://example.org/foo/bar>
-PASS Parsing origin: <ssh://example.com/foo/bar.git> against <http://example.org/>
-PASS Parsing origin: <http://example.com/././foo> against <about:blank>
-PASS Parsing origin: <http://example.com/./.foo> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/.> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/./> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/..bar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../ton> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/../../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/../../../ton> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e%2> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
-PASS Parsing origin: <http://example.com////../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar//../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar//..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo> against <about:blank>
-PASS Parsing origin: <http://example.com/%20foo> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2zbar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2©zbar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%41%7a> against <about:blank>
-PASS Parsing origin: <http://example.com/foo	‘%91> against <about:blank>
-FAIL Parsing origin: <http://example.com/foo%00%51> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
-PASS Parsing origin: <http://example.com/(%28:%3A%29)> against <about:blank>
-PASS Parsing origin: <http://example.com/%3A%3a%3C%3c> against <about:blank>
-PASS Parsing origin: <http://example.com/foo	bar> against <about:blank>
-PASS Parsing origin: <http://example.com\\foo\\bar> against <about:blank>
-PASS Parsing origin: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
-PASS Parsing origin: <http://example.com/@asdf%40> against <about:blank>
-PASS Parsing origin: <http://example.com/你好你好> against <about:blank>
-PASS Parsing origin: <http://example.com/‥/foo> against <about:blank>
-PASS Parsing origin: <http://example.com//foo> against <about:blank>
-PASS Parsing origin: <http://example.com/‮/foo/‭/bar> against <about:blank>
-PASS Parsing origin: <http://www.google.com/foo?bar=baz#> against <about:blank>
-PASS Parsing origin: <http://www.google.com/foo?bar=baz# »> against <about:blank>
-PASS Parsing origin: <data:test# »> against <about:blank>
-PASS Parsing origin: <http://www.google.com> against <about:blank>
-PASS Parsing origin: <http://192.0x00A80001> against <about:blank>
-PASS Parsing origin: <http://www/foo%2Ehtml> against <about:blank>
-PASS Parsing origin: <http://www/foo/%2E/html> against <about:blank>
-PASS Parsing origin: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
-PASS Parsing origin: <http:\\www.google.com\foo> against <about:blank>
-PASS Parsing origin: <http://foo:80/> against <about:blank>
-PASS Parsing origin: <http://foo:81/> against <about:blank>
-PASS Parsing origin: <httpa://foo:80/> against <about:blank>
-PASS Parsing origin: <https://foo:443/> against <about:blank>
-PASS Parsing origin: <https://foo:80/> against <about:blank>
-PASS Parsing origin: <ftp://foo:21/> against <about:blank>
-PASS Parsing origin: <ftp://foo:80/> against <about:blank>
-PASS Parsing origin: <gopher://foo:70/> against <about:blank>
-PASS Parsing origin: <gopher://foo:443/> against <about:blank>
-PASS Parsing origin: <ws://foo:80/> against <about:blank>
-PASS Parsing origin: <ws://foo:81/> against <about:blank>
-PASS Parsing origin: <ws://foo:443/> against <about:blank>
-PASS Parsing origin: <ws://foo:815/> against <about:blank>
-PASS Parsing origin: <wss://foo:80/> against <about:blank>
-PASS Parsing origin: <wss://foo:81/> against <about:blank>
-PASS Parsing origin: <wss://foo:443/> against <about:blank>
-PASS Parsing origin: <wss://foo:815/> against <about:blank>
-PASS Parsing origin: <http:/example.com/> against <about:blank>
-PASS Parsing origin: <ftp:/example.com/> against <about:blank>
-PASS Parsing origin: <https:/example.com/> against <about:blank>
-PASS Parsing origin: <madeupscheme:/example.com/> against <about:blank>
-PASS Parsing origin: <ftps:/example.com/> against <about:blank>
-PASS Parsing origin: <gopher:/example.com/> against <about:blank>
-PASS Parsing origin: <ws:/example.com/> against <about:blank>
-PASS Parsing origin: <wss:/example.com/> against <about:blank>
-PASS Parsing origin: <data:/example.com/> against <about:blank>
-PASS Parsing origin: <javascript:/example.com/> against <about:blank>
-PASS Parsing origin: <mailto:/example.com/> against <about:blank>
-PASS Parsing origin: <http:example.com/> against <about:blank>
-PASS Parsing origin: <ftp:example.com/> against <about:blank>
-PASS Parsing origin: <https:example.com/> against <about:blank>
-PASS Parsing origin: <madeupscheme:example.com/> against <about:blank>
-PASS Parsing origin: <ftps:example.com/> against <about:blank>
-PASS Parsing origin: <gopher:example.com/> against <about:blank>
-PASS Parsing origin: <ws:example.com/> against <about:blank>
-PASS Parsing origin: <wss:example.com/> against <about:blank>
-PASS Parsing origin: <data:example.com/> against <about:blank>
-PASS Parsing origin: <javascript:example.com/> against <about:blank>
-PASS Parsing origin: <mailto:example.com/> against <about:blank>
-PASS Parsing origin: <http:@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/@www.example.com> against <about:blank>
-PASS Parsing origin: <http://@www.example.com> against <about:blank>
-PASS Parsing origin: <http:a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://@pple.com> against <about:blank>
-PASS Parsing origin: <http::b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http://a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http://www.@pple.com> against <about:blank>
-PASS Parsing origin: <http://:@www.example.com> against <about:blank>
-PASS Parsing origin: </> against <http://www.example.com/test>
-PASS Parsing origin: </test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <.> against <http://www.example.com/test>
-PASS Parsing origin: <..> against <http://www.example.com/test>
-PASS Parsing origin: <test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <./test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../aaa/test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../../test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <中/test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <http://www.example2.com> against <http://www.example.com/test>
-PASS Parsing origin: <//www.example2.com> against <http://www.example.com/test>
-PASS Parsing origin: <http://ExAmPlE.CoM> against <http://other.com/>
-PASS Parsing origin: <http://GOO​⁠goo.com> against <http://other.com/>
-PASS Parsing origin: <\0 http://example.com/ \r > against <about:blank>
-PASS Parsing origin: <http://www.foo。bar.com> against <http://other.com/>
-PASS Parsing origin: <https://x/�?�#�> against <about:blank>
-PASS Parsing origin: <http://Go.com> against <http://other.com/>
-PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
-PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
-PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
-PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
-PASS Parsing origin: <http://0Xc0.0250.01> against <http://other.com/>
-PASS Parsing origin: <http://./> against <about:blank>
-PASS Parsing origin: <http://../> against <about:blank>
-PASS Parsing origin: <http://foo:💩@example.com/bar> against <http://other.com/>
-PASS Parsing origin: <#> against <test:test>
-PASS Parsing origin: <#x> against <mailto:x@x.com>
-PASS Parsing origin: <#x> against <data:,>
-PASS Parsing origin: <#x> against <about:blank>
-PASS Parsing origin: <#> against <test:test?test>
-PASS Parsing origin: <https://@test@test@example:800/> against <http://doesnotmatter/>
-PASS Parsing origin: <https://@@@example> against <http://doesnotmatter/>
-PASS Parsing origin: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
-PASS Parsing origin: <http://host/?'> against <about:blank>
-PASS Parsing origin: <notspecial://host/?'> against <about:blank>
-PASS Parsing origin: </some/path> against <http://user@example.org/smth>
-PASS Parsing origin: <> against <http://user:pass@example.org:21/smth>
-PASS Parsing origin: </some/path> against <http://user:pass@example.org:21/smth>
-PASS Parsing origin: <i> against <sc:/pa/pa>
-PASS Parsing origin: <i> against <sc://ho/pa>
-PASS Parsing origin: <i> against <sc:///pa/pa>
-PASS Parsing origin: <../i> against <sc:/pa/pa>
-PASS Parsing origin: <../i> against <sc://ho/pa>
-PASS Parsing origin: <../i> against <sc:///pa/pa>
-PASS Parsing origin: </i> against <sc:/pa/pa>
-PASS Parsing origin: </i> against <sc://ho/pa>
-PASS Parsing origin: </i> against <sc:///pa/pa>
-PASS Parsing origin: <?i> against <sc:/pa/pa>
-PASS Parsing origin: <?i> against <sc://ho/pa>
-PASS Parsing origin: <?i> against <sc:///pa/pa>
-PASS Parsing origin: <#i> against <sc:sd>
-PASS Parsing origin: <#i> against <sc:sd/sd>
-PASS Parsing origin: <#i> against <sc:/pa/pa>
-PASS Parsing origin: <#i> against <sc://ho/pa>
-PASS Parsing origin: <#i> against <sc:///pa/pa>
-PASS Parsing origin: <about:/../> against <about:blank>
-PASS Parsing origin: <data:/../> against <about:blank>
-PASS Parsing origin: <javascript:/../> against <about:blank>
-PASS Parsing origin: <mailto:/../> against <about:blank>
-PASS Parsing origin: <sc://ñ.test/> against <about:blank>
-PASS Parsing origin: <x> against <sc://ñ>
-PASS Parsing origin: <sc:\../> against <about:blank>
-PASS Parsing origin: <sc::a@example.net> against <about:blank>
-PASS Parsing origin: <wow:%NBD> against <about:blank>
-PASS Parsing origin: <wow:%1G> against <about:blank>
-PASS Parsing origin: <wow:￿> against <about:blank>
-FAIL Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
-FAIL Parsing origin: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "http://\x01\x02\x03\x04\x05\x06\x07\b\v\f\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f!\"$&'()*+,-.;=_`{}~" but got "null"
-PASS Parsing origin: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
-PASS Parsing origin: <ftp://%e2%98%83> against <about:blank>
-PASS Parsing origin: <https://%e2%98%83> against <about:blank>
-PASS Parsing origin: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
-PASS Parsing origin: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
-PASS Parsing origin: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
-PASS Parsing origin: <h	t
-t\rp://h	o
-s\rt:9	0
-0\r0/p	a
-t\rh?q	u
-e\rry#f	r
-a\rg> against <about:blank>
-PASS Parsing origin: <?a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing origin: <??a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:> against <http://example.org/foo/bar>
-PASS Parsing origin: <sc:> against <https://example.org/foo/bar>
-PASS Parsing origin: <http://foo.bar/baz?qux#foobar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
-PASS Parsing origin: <http://1.2.3.4/> against <http://other.com/>
-PASS Parsing origin: <http://1.2.3.4./> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257.> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257.com> against <http://other.com/>
-PASS Parsing origin: <http://256> against <http://other.com/>
-PASS Parsing origin: <http://256.com> against <http://other.com/>
-PASS Parsing origin: <http://999999999> against <http://other.com/>
-PASS Parsing origin: <http://999999999.> against <http://other.com/>
-PASS Parsing origin: <http://999999999.com> against <http://other.com/>
-PASS Parsing origin: <http://10000000000.com> against <http://other.com/>
-PASS Parsing origin: <http://4294967295> against <http://other.com/>
-PASS Parsing origin: <http://0xffffffff> against <http://other.com/>
-PASS Parsing origin: <https://0x.0x.0> against <about:blank>
-PASS Parsing origin: <asdf://%43%7C/> against <about:blank>
-PASS Parsing origin: <http://[1:0::]> against <http://example.net/>
-PASS Parsing origin: <sc://ñ> against <about:blank>
-PASS Parsing origin: <sc://ñ?x> against <about:blank>
-PASS Parsing origin: <sc://ñ#x> against <about:blank>
-PASS Parsing origin: <#x> against <sc://ñ>
-PASS Parsing origin: <?x> against <sc://ñ>
-PASS Parsing origin: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
-PASS Parsing origin: <telnet://user:pass@foobar.com:23/> against <about:blank>
-PASS Parsing origin: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
-PASS Parsing origin: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
-PASS Parsing origin: <rsync://foo@host:911/sup> against <about:blank>
-PASS Parsing origin: <git://github.com/foo/bar.git> against <about:blank>
-PASS Parsing origin: <irc://myserver.com:6999/channel?passwd> against <about:blank>
-PASS Parsing origin: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
-PASS Parsing origin: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
-PASS Parsing origin: <git+https://github.com/foo/bar> against <about:blank>
-PASS Parsing origin: <urn:ietf:rfc:2648> against <about:blank>
-PASS Parsing origin: <tag:joe@example.org,2001:foo/bar> against <about:blank>
-PASS Parsing origin: <blob:https://example.com:443/> against <about:blank>
-PASS Parsing origin: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
-PASS Parsing origin: <blob:> against <about:blank>
-PASS Parsing origin: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
-PASS Parsing origin: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
-PASS Parsing origin: <https://user:pass[@foo/bar> against <http://example.org>
-PASS Parsing origin: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
-FAIL Parsing origin: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "wss://!\"$&'()*+,-.;=_`{}~" but got "null"
-PASS Parsing origin: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/css/css-pseudo/idlharness-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/css/css-pseudo/idlharness-expected.txt
deleted file mode 100644
index 46168b2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/css/css-pseudo/idlharness-expected.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-This is a testharness.js-based test.
-FAIL idl_test setup promise_test: Unhandled rejection with value: object "TypeError: window.getPseudoElements is not a function"
-PASS idl_test validation
-PASS Partial interface Element: original interface defined
-PASS Partial interface Element: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-FAIL CSSPseudoElement interface: existence and properties of interface object assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface object length assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface object name assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: attribute type assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement interface: attribute element assert_own_property: self does not have own property "CSSPseudoElement" expected property "CSSPseudoElement" missing
-FAIL CSSPseudoElement must be primary interface of beforeElements.item(0) assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL Stringification of beforeElements.item(0) assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL CSSPseudoElement interface: beforeElements.item(0) must inherit property "element" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: beforeElements is not defined"
-FAIL Element interface: operation pseudo(CSSOMString) assert_own_property: interface prototype object missing non-static operation expected property "pseudo" missing
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-expected.txt
deleted file mode 100644
index f4e57be3..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-expected.txt
+++ /dev/null
@@ -1,684 +0,0 @@
-This is a testharness.js-based test.
-Found 669 tests; 383 PASS, 286 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Loading data…
-PASS Parsing: <http://example	.
-org> against <http://example.org/foo/bar>
-PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
-PASS Parsing: <https://test:@test> against <about:blank>
-PASS Parsing: <https://:@test> against <about:blank>
-FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
-FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
-PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
-PASS Parsing: <	   :foo.com   
-> against <http://example.org/foo/bar>
-PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
-PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
-PASS Parsing: <lolscheme:x x#x x> against <about:blank>
-PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
-FAIL Parsing: <http://f: /c> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: /c" but got "http://f:%20/c"
-PASS Parsing: <http://f:
-/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
-PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
-FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: 21 / b ? d # e " but got "http://f:%2021%20/%20b%20?%20d%20#%20e"
-PASS Parsing: <> against <http://example.org/foo/bar>
-PASS Parsing: <  	> against <http://example.org/foo/bar>
-PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
-PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <:> against <http://example.org/foo/bar>
-PASS Parsing: <:a> against <http://example.org/foo/bar>
-PASS Parsing: <:/> against <http://example.org/foo/bar>
-PASS Parsing: <:\> against <http://example.org/foo/bar>
-PASS Parsing: <:#> against <http://example.org/foo/bar>
-PASS Parsing: <#> against <http://example.org/foo/bar>
-PASS Parsing: <#/> against <http://example.org/foo/bar>
-PASS Parsing: <#\> against <http://example.org/foo/bar>
-PASS Parsing: <#;?> against <http://example.org/foo/bar>
-PASS Parsing: <?> against <http://example.org/foo/bar>
-PASS Parsing: </> against <http://example.org/foo/bar>
-PASS Parsing: <:23> against <http://example.org/foo/bar>
-PASS Parsing: </:23> against <http://example.org/foo/bar>
-PASS Parsing: <\x> against <http://example.org/foo/bar>
-PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
-PASS Parsing: <::> against <http://example.org/foo/bar>
-PASS Parsing: <::23> against <http://example.org/foo/bar>
-FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
-PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
-PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
-PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
-PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
-PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
-PASS Parsing: <foo:/> against <http://example.org/foo/bar>
-PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
-FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
-FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
-FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
-PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
-PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
-PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
-PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
-PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
-FAIL Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://[1::2]:3:4" but got "http://[1::2]:3:4/"
-FAIL Parsing: <http://2001::1> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1" but got "http://2001::1/"
-FAIL Parsing: <http://2001::1]> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]" but got "http://2001::1]/"
-FAIL Parsing: <http://2001::1]:80> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]:80" but got "http://2001::1]/"
-PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
-PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
-PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <file://example:1/> against <about:blank>
-PASS Parsing: <file://example:test/> against <about:blank>
-FAIL Parsing: <file://example%/> against <about:blank> assert_equals: failure should set href to input expected "file://example%/" but got "file://example%25/"
-PASS Parsing: <file://[example]/> against <about:blank>
-PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
-PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
-PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
-PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
-PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
-PASS Parsing: <#β> against <http://example.org/foo/bar>
-PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
-PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
-FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
-FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
-FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
-FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
-FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
-FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
-PASS Parsing: <//server/file> against <file:///tmp/mock/path>
-PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
-PASS Parsing: </\server/file> against <file:///tmp/mock/path>
-PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
-PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
-PASS Parsing: <//> against <file:///tmp/mock/path>
-PASS Parsing: <///> against <file:///tmp/mock/path>
-PASS Parsing: <///test> against <file:///tmp/mock/path>
-PASS Parsing: <file://test> against <file:///tmp/mock/path>
-FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
-FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
-FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
-PASS Parsing: <test> against <file:///tmp/mock/path>
-PASS Parsing: <file:test> against <file:///tmp/mock/path>
-PASS Parsing: <http://example.com/././foo> against <about:blank>
-PASS Parsing: <http://example.com/./.foo> against <about:blank>
-PASS Parsing: <http://example.com/foo/.> against <about:blank>
-PASS Parsing: <http://example.com/foo/./> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
-PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
-PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
-PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
-FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
-FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
-PASS Parsing: <http://example.com////../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
-PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
-PASS Parsing: <http://example.com/foo> against <about:blank>
-PASS Parsing: <http://example.com/%20foo> against <about:blank>
-PASS Parsing: <http://example.com/foo%> against <about:blank>
-PASS Parsing: <http://example.com/foo%2> against <about:blank>
-PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
-PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
-FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
-PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
-FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> assert_equals: href expected "http://example.com/foo%00%51" but got "http://example.com/foo%00Q"
-PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
-PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
-PASS Parsing: <http://example.com/foo	bar> against <about:blank>
-PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
-PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
-PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
-PASS Parsing: <http://example.com/你好你好> against <about:blank>
-PASS Parsing: <http://example.com/‥/foo> against <about:blank>
-PASS Parsing: <http://example.com//foo> against <about:blank>
-PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
-PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
-PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
-PASS Parsing: <data:test# »> against <about:blank>
-PASS Parsing: <http://www.google.com> against <about:blank>
-PASS Parsing: <http://192.0x00A80001> against <about:blank>
-FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
-PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
-PASS Parsing: <http://user:pass@/> against <about:blank>
-PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
-PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
-PASS Parsing: <http://foo:80/> against <about:blank>
-PASS Parsing: <http://foo:81/> against <about:blank>
-FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
-PASS Parsing: <http://foo:-80/> against <about:blank>
-PASS Parsing: <https://foo:443/> against <about:blank>
-PASS Parsing: <https://foo:80/> against <about:blank>
-PASS Parsing: <ftp://foo:21/> against <about:blank>
-PASS Parsing: <ftp://foo:80/> against <about:blank>
-FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
-FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
-PASS Parsing: <ws://foo:80/> against <about:blank>
-PASS Parsing: <ws://foo:81/> against <about:blank>
-PASS Parsing: <ws://foo:443/> against <about:blank>
-PASS Parsing: <ws://foo:815/> against <about:blank>
-PASS Parsing: <wss://foo:80/> against <about:blank>
-PASS Parsing: <wss://foo:81/> against <about:blank>
-PASS Parsing: <wss://foo:443/> against <about:blank>
-PASS Parsing: <wss://foo:815/> against <about:blank>
-PASS Parsing: <http:/example.com/> against <about:blank>
-PASS Parsing: <ftp:/example.com/> against <about:blank>
-PASS Parsing: <https:/example.com/> against <about:blank>
-PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
-PASS Parsing: <file:/example.com/> against <about:blank>
-PASS Parsing: <ftps:/example.com/> against <about:blank>
-PASS Parsing: <gopher:/example.com/> against <about:blank>
-PASS Parsing: <ws:/example.com/> against <about:blank>
-PASS Parsing: <wss:/example.com/> against <about:blank>
-PASS Parsing: <data:/example.com/> against <about:blank>
-PASS Parsing: <javascript:/example.com/> against <about:blank>
-PASS Parsing: <mailto:/example.com/> against <about:blank>
-PASS Parsing: <http:example.com/> against <about:blank>
-PASS Parsing: <ftp:example.com/> against <about:blank>
-PASS Parsing: <https:example.com/> against <about:blank>
-PASS Parsing: <madeupscheme:example.com/> against <about:blank>
-PASS Parsing: <ftps:example.com/> against <about:blank>
-PASS Parsing: <gopher:example.com/> against <about:blank>
-PASS Parsing: <ws:example.com/> against <about:blank>
-PASS Parsing: <wss:example.com/> against <about:blank>
-PASS Parsing: <data:example.com/> against <about:blank>
-PASS Parsing: <javascript:example.com/> against <about:blank>
-PASS Parsing: <mailto:example.com/> against <about:blank>
-PASS Parsing: <http:@www.example.com> against <about:blank>
-PASS Parsing: <http:/@www.example.com> against <about:blank>
-PASS Parsing: <http://@www.example.com> against <about:blank>
-PASS Parsing: <http:a:b@www.example.com> against <about:blank>
-PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
-PASS Parsing: <http://a:b@www.example.com> against <about:blank>
-PASS Parsing: <http://@pple.com> against <about:blank>
-PASS Parsing: <http::b@www.example.com> against <about:blank>
-PASS Parsing: <http:/:b@www.example.com> against <about:blank>
-PASS Parsing: <http://:b@www.example.com> against <about:blank>
-FAIL Parsing: <http:/:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/:@/www.example.com" but got "http:///www.example.com"
-PASS Parsing: <http://user@/www.example.com> against <about:blank>
-FAIL Parsing: <http:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <http:/@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <http://@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@/www.example.com" but got "http:///www.example.com"
-FAIL Parsing: <https:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "https:@/www.example.com" but got "https:///www.example.com"
-FAIL Parsing: <http:a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:a:b@/www.example.com" but got "http://a:b@/www.example.com"
-FAIL Parsing: <http:/a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/a:b@/www.example.com" but got "http://a:b@/www.example.com"
-PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
-FAIL Parsing: <http::@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http::@/www.example.com" but got "http:///www.example.com"
-PASS Parsing: <http:a:@www.example.com> against <about:blank>
-PASS Parsing: <http:/a:@www.example.com> against <about:blank>
-PASS Parsing: <http://a:@www.example.com> against <about:blank>
-PASS Parsing: <http://www.@pple.com> against <about:blank>
-FAIL Parsing: <http:@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@:www.example.com" but got "http://:www.example.com/"
-FAIL Parsing: <http:/@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@:www.example.com" but got "http://:www.example.com/"
-FAIL Parsing: <http://@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@:www.example.com" but got "http://:www.example.com/"
-PASS Parsing: <http://:@www.example.com> against <about:blank>
-PASS Parsing: </> against <http://www.example.com/test>
-PASS Parsing: </test.txt> against <http://www.example.com/test>
-PASS Parsing: <.> against <http://www.example.com/test>
-PASS Parsing: <..> against <http://www.example.com/test>
-PASS Parsing: <test.txt> against <http://www.example.com/test>
-PASS Parsing: <./test.txt> against <http://www.example.com/test>
-PASS Parsing: <../test.txt> against <http://www.example.com/test>
-PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
-PASS Parsing: <../../test.txt> against <http://www.example.com/test>
-PASS Parsing: <中/test.txt> against <http://www.example.com/test>
-PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
-PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
-PASS Parsing: <file:...> against <http://www.example.com/test>
-PASS Parsing: <file:..> against <http://www.example.com/test>
-PASS Parsing: <file:a> against <http://www.example.com/test>
-PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
-FAIL Parsing: <http://example example.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[]" but got "http://[]/"
-FAIL Parsing: <http://[:]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[:]" but got "http://[:]/"
-FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
-PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
-PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
-FAIL Parsing: <http://﷐zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://﷐zyx.com" but got "http://%EF%BF%BDzyx.com/"
-FAIL Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%b7%90zyx.com" but got "http://%EF%BF%BDzyx.com/"
-FAIL Parsing: <https://�> against <about:blank> assert_equals: failure should set href to input expected "https://\ufffd" but got "https://%EF%BF%BD/"
-FAIL Parsing: <https://%EF%BF%BD> against <about:blank> assert_equals: failure should set href to input expected "https://%EF%BF%BD" but got "https://%EF%BF%BD/"
-PASS Parsing: <https://x/�?�#�> against <about:blank>
-FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://Go.com> against <http://other.com/>
-FAIL Parsing: <http://%41.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
-FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
-PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
-FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
-FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
-FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
-FAIL Parsing: <http://hello%00> against <http://other.com/> assert_equals: failure should set href to input expected "http://hello%00" but got "http://hello%00/"
-PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
-PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
-FAIL Parsing: <http://192.168.0.257> against <http://other.com/> assert_equals: failure should set href to input expected "http://192.168.0.257" but got "http://192.168.0.257/"
-FAIL Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/> assert_equals: failure should set href to input expected "http://%3g%78%63%30%2e%30%32%35%30%2E.01" but got "http://%253gxc0.0250..01/"
-FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <https://x x:12> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
-PASS Parsing: <http://./> against <about:blank>
-PASS Parsing: <http://../> against <about:blank>
-PASS Parsing: <http://[www.google.com]/> against <about:blank>
-FAIL Parsing: <http://[google.com]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[google.com]" but got "http://[google.com]/"
-FAIL Parsing: <http://[::1.2.3.4x]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[::1.2.3.4x]" but got "http://[::1.2.3.4x]/"
-FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
-PASS Parsing: <#> against <test:test>
-PASS Parsing: <#x> against <mailto:x@x.com>
-FAIL Parsing: <#x> against <data:,> assert_equals: href expected "data:,#x" but got "mailto:x@x.com#x"
-PASS Parsing: <#x> against <about:blank>
-PASS Parsing: <#> against <test:test?test>
-PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
-PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
-PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
-PASS Parsing: <http://host/?'> against <about:blank>
-FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
-PASS Parsing: </some/path> against <http://user@example.org/smth>
-PASS Parsing: <> against <http://user:pass@example.org:21/smth>
-PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
-FAIL Parsing: <i> against <sc:sd> assert_equals: failure should set href to input expected "i" but got ""
-FAIL Parsing: <i> against <sc:sd/sd> assert_equals: failure should set href to input expected "i" but got ""
-PASS Parsing: <i> against <sc:/pa/pa>
-FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
-FAIL Parsing: <../i> against <sc:sd> assert_equals: failure should set href to input expected "../i" but got ""
-FAIL Parsing: <../i> against <sc:sd/sd> assert_equals: failure should set href to input expected "../i" but got ""
-PASS Parsing: <../i> against <sc:/pa/pa>
-FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
-FAIL Parsing: </i> against <sc:sd> assert_equals: failure should set href to input expected "/i" but got ""
-FAIL Parsing: </i> against <sc:sd/sd> assert_equals: failure should set href to input expected "/i" but got ""
-PASS Parsing: </i> against <sc:/pa/pa>
-FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
-FAIL Parsing: <?i> against <sc:sd> assert_equals: failure should set href to input expected "?i" but got ""
-FAIL Parsing: <?i> against <sc:sd/sd> assert_equals: failure should set href to input expected "?i" but got ""
-PASS Parsing: <?i> against <sc:/pa/pa>
-FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
-PASS Parsing: <#i> against <sc:sd>
-PASS Parsing: <#i> against <sc:sd/sd>
-PASS Parsing: <#i> against <sc:/pa/pa>
-FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
-FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
-FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
-FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
-FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
-FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
-FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
-FAIL Parsing: <sc://\0/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc:// /> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
-FAIL Parsing: <sc://@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://:/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://:12/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://[/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://\/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <sc://]/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1/x" but got "sc://%C3%B1"
-PASS Parsing: <sc:\../> against <about:blank>
-PASS Parsing: <sc::a@example.net> against <about:blank>
-PASS Parsing: <wow:%NBD> against <about:blank>
-PASS Parsing: <wow:%1G> against <about:blank>
-FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
-FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: href expected "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF" but got "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD"
-FAIL Parsing: <http://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://a^b> against <about:blank> assert_equals: failure should set href to input expected "http://a^b" but got "http://a%5Eb/"
-FAIL Parsing: <non-special://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <non-special://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <non-special://a^b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <foo://ho\0st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <foo://ho|st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
-FAIL Parsing: <foo://ho
-st/> against <about:blank> assert_equals: host expected "host" but got ""
-FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
-PASS Parsing: <http://ho%00st/> against <about:blank>
-PASS Parsing: <http://ho%09st/> against <about:blank>
-PASS Parsing: <http://ho%0Ast/> against <about:blank>
-PASS Parsing: <http://ho%0Dst/> against <about:blank>
-FAIL Parsing: <http://ho%20st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%23st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://ho%2Fst/> against <about:blank>
-FAIL Parsing: <http://ho%3Ast/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%3Ast/" but got "http://ho:st/"
-FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <http://ho%3Fst/> against <about:blank>
-FAIL Parsing: <http://ho%40st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://ho%5Bst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Bst/" but got "http://ho[st/"
-PASS Parsing: <http://ho%5Cst/> against <about:blank>
-FAIL Parsing: <http://ho%5Dst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Dst/" but got "http://ho]st/"
-FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "http://\x1f!\"$&'()*+,-.;=_`{}~/" but got "http://%1F%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
-FAIL Parsing: <sc://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%1F!\"$&'()*+,-.;=_`{}~" but got ""
-FAIL Parsing: <ftp://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%80/" but got "ftp://example.com%EF%BF%BD/"
-FAIL Parsing: <ftp://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%A0/" but got "ftp://example.com%EF%BF%BD/"
-FAIL Parsing: <https://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%80/" but got "https://example.com%EF%BF%BD/"
-FAIL Parsing: <https://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%A0/" but got "https://example.com%EF%BF%BD/"
-PASS Parsing: <ftp://%e2%98%83> against <about:blank>
-PASS Parsing: <https://%e2%98%83> against <about:blank>
-PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
-PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
-PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
-PASS Parsing: <h	t
-t\rp://h	o
-s\rt:9	0
-0\r0/p	a
-t\rh?q	u
-e\rry#f	r
-a\rg> against <about:blank>
-PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing: <http:> against <http://example.org/foo/bar>
-PASS Parsing: <http:> against <https://example.org/foo/bar>
-PASS Parsing: <sc:> against <https://example.org/foo/bar>
-PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
-PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
-PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
-PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
-PASS Parsing: <http://192.168.257> against <http://other.com/>
-PASS Parsing: <http://192.168.257.> against <http://other.com/>
-PASS Parsing: <http://192.168.257.com> against <http://other.com/>
-PASS Parsing: <http://256> against <http://other.com/>
-PASS Parsing: <http://256.com> against <http://other.com/>
-PASS Parsing: <http://999999999> against <http://other.com/>
-PASS Parsing: <http://999999999.> against <http://other.com/>
-PASS Parsing: <http://999999999.com> against <http://other.com/>
-FAIL Parsing: <http://10000000000> against <http://other.com/> assert_equals: failure should set href to input expected "http://10000000000" but got "http://10000000000/"
-PASS Parsing: <http://10000000000.com> against <http://other.com/>
-PASS Parsing: <http://4294967295> against <http://other.com/>
-FAIL Parsing: <http://4294967296> against <http://other.com/> assert_equals: failure should set href to input expected "http://4294967296" but got "http://4294967296/"
-PASS Parsing: <http://0xffffffff> against <http://other.com/>
-FAIL Parsing: <http://0xffffffff1> against <http://other.com/> assert_equals: failure should set href to input expected "http://0xffffffff1" but got "http://0xffffffff1/"
-FAIL Parsing: <http://256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256" but got "http://256.256.256.256/"
-PASS Parsing: <https://0x.0x.0> against <about:blank>
-PASS Parsing: <https://0x100000000/test> against <about:blank>
-PASS Parsing: <https://256.0.0.1/test> against <about:blank>
-PASS Parsing: <file:///C%3A/> against <about:blank>
-PASS Parsing: <file:///C%7C/> against <about:blank>
-FAIL Parsing: <file://%43%3A> against <about:blank> assert_equals: failure should set href to input expected "file://%43%3A" but got "file://c:/"
-FAIL Parsing: <file://%43%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://%43|> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://C%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <file://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <https://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <asdf://%43|/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
-PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
-FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
-PASS Parsing: <..> against <file:///>
-FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
-FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
-PASS Parsing: </> against <file://h/a/b>
-FAIL Parsing: <//d:> against <file:///C:/a/b> assert_equals: href expected "file:///d:" but got "file://d:/"
-FAIL Parsing: <//d:/..> against <file:///C:/a/b> assert_equals: href expected "file:///d:/" but got "file://d:/"
-PASS Parsing: <..> against <file:///ab:/>
-PASS Parsing: <..> against <file:///1:/>
-PASS Parsing: <> against <file:///test?test#test>
-PASS Parsing: <file:> against <file:///test?test#test>
-PASS Parsing: <?x> against <file:///test?test#test>
-PASS Parsing: <file:?x> against <file:///test?test#test>
-PASS Parsing: <#x> against <file:///test?test#test>
-PASS Parsing: <file:#x> against <file:///test?test#test>
-FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
-FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
-FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
-FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
-PASS Parsing: <file://spider///> against <about:blank>
-FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
-PASS Parsing: <file:///localhost//cat> against <about:blank>
-FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
-FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
-FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
-PASS Parsing: <\//pig> against <file://lion/>
-FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
-FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
-PASS Parsing: </..//localhost//pig> against <file://lion/>
-PASS Parsing: <file://> against <file://ape/>
-PASS Parsing: </rooibos> against <file://tea/>
-PASS Parsing: </?chai> against <file://tea/>
-FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
-FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
-FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
-FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
-FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-FAIL Parsing: <C|
-/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
-PASS Parsing: <C> against <file://host/dir/file>
-FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
-PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
-FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
-PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
-PASS Parsing: </c:/foo/bar> against <file://host/path>
-PASS Parsing: <file://example.net/C:/> against <about:blank>
-PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
-PASS Parsing: <file://[1::8]/C:/> against <about:blank>
-FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
-PASS Parsing: </C:/> against <file://host/>
-PASS Parsing: <file:C:/> against <file://host/>
-PASS Parsing: <file:/C:/> against <file://host/>
-FAIL Parsing: <//C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
-FAIL Parsing: <file://C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
-PASS Parsing: <///C:/> against <file://host/>
-PASS Parsing: <file:///C:/> against <file://host/>
-FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
-FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
-PASS Parsing: <file:> against <about:blank>
-PASS Parsing: <file:?q=v> against <about:blank>
-PASS Parsing: <file:#frag> against <about:blank>
-PASS Parsing: <file:///Y:> against <about:blank>
-PASS Parsing: <file:///Y:/> against <about:blank>
-PASS Parsing: <file:///./Y> against <about:blank>
-PASS Parsing: <file:///./Y:> against <about:blank>
-FAIL Parsing: <\\\.\Y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\Y:" but got ""
-PASS Parsing: <file:///y:> against <about:blank>
-PASS Parsing: <file:///y:/> against <about:blank>
-PASS Parsing: <file:///./y> against <about:blank>
-PASS Parsing: <file:///./y:> against <about:blank>
-FAIL Parsing: <\\\.\y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\y:" but got ""
-FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
-FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
-FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
-PASS Parsing: <file:///one/two> against <file:///>
-FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
-PASS Parsing: <//one/two> against <file:///>
-PASS Parsing: <///one/two> against <file:///>
-FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
-PASS Parsing: <file:///.//> against <file:////>
-PASS Parsing: <file:.//p> against <about:blank>
-PASS Parsing: <file:/.//p> against <about:blank>
-PASS Parsing: <http://[1:0::]> against <http://example.net/>
-FAIL Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/> assert_equals: failure should set href to input expected "http://[0:1:2:3:4:5:6:7:8]" but got "http://[0:1:2:3:4:5:6:7:8]/"
-FAIL Parsing: <https://[0::0::0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0::0::0]" but got "https://[0::0::0]/"
-FAIL Parsing: <https://[0:.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:.0]" but got "https://[0:.0]/"
-FAIL Parsing: <https://[0:0:]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:0:]" but got "https://[0:0:]/"
-FAIL Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1:2:3:4:5:6:7.0.0.0.1]" but got "https://[0:1:2:3:4:5:6:7.0.0.0.1]/"
-FAIL Parsing: <https://[0:1.00.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.00.0.0.0]" but got "https://[0:1.00.0.0.0]/"
-FAIL Parsing: <https://[0:1.290.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.290.0.0.0]" but got "https://[0:1.290.0.0.0]/"
-FAIL Parsing: <https://[0:1.23.23]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.23.23]" but got "https://[0:1.23.23]/"
-FAIL Parsing: <http://?> against <about:blank> assert_equals: failure should set href to input expected "http://?" but got "http:/?"
-FAIL Parsing: <http://#> against <about:blank> assert_equals: failure should set href to input expected "http://#" but got "http:/#"
-PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
-PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
-PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
-FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
-FAIL Parsing: <#x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1#x" but got "sc://%C3%B1"
-FAIL Parsing: <?x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1?x" but got "sc://%C3%B1"
-FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
-FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
-FAIL Parsing: <///> against <sc://x/> assert_equals: href expected "sc:///" but got "sc:"
-FAIL Parsing: <////> against <sc://x/> assert_equals: href expected "sc:////" but got "sc:"
-FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
-FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
-FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
-FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
-FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
-FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
-FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
-FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
-FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
-FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
-FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
-PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
-PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
-FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
-FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
-FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
-FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
-FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
-FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
-FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
-FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
-FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
-FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
-FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
-FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
-FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
-FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
-FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
-FAIL Parsing: <non-special://[:80/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <blob:https://example.com:443/> against <about:blank>
-PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
-PASS Parsing: <blob:> against <about:blank>
-PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
-PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
-FAIL Parsing: <http://[::127.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "http://[::127.0.0.0.1]" but got "http://[::127.0.0.0.1]/"
-PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
-PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
-PASS Parsing: <http://example.org/test?"> against <about:blank>
-PASS Parsing: <http://example.org/test?#> against <about:blank>
-PASS Parsing: <http://example.org/test?<> against <about:blank>
-PASS Parsing: <http://example.org/test?>> against <about:blank>
-PASS Parsing: <http://example.org/test?⌣> against <about:blank>
-PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
-PASS Parsing: <http://example.org/test?%GH> against <about:blank>
-PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
-PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
-FAIL Parsing: <a> against <about:blank> assert_equals: failure should set href to input expected "a" but got ""
-FAIL Parsing: <a/> against <about:blank> assert_equals: failure should set href to input expected "a/" but got ""
-FAIL Parsing: <a//> against <about:blank> assert_equals: failure should set href to input expected "a//" but got ""
-FAIL Parsing: <test-a-colon.html> against <a:> assert_equals: failure should set href to input expected "test-a-colon.html" but got ""
-FAIL Parsing: <test-a-colon-b.html> against <a:b> assert_equals: failure should set href to input expected "test-a-colon-b.html" but got ""
-PASS Parsing: <test-a-colon-slash.html> against <a:/>
-FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> assert_equals: href expected "a:///test-a-colon-slash-slash.html" but got ""
-PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
-FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> assert_equals: href expected "a://b/test-a-colon-slash-slash-b.html" but got "a://b"
-PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
-FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
-PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
-PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
-PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
-PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
-PASS Parsing: <file://a­b/p> against <about:blank>
-PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
-FAIL Parsing: <file://­/p> against <about:blank> assert_equals: failure should set href to input expected "file://­/p" but got "file://%C2%AD/p"
-PASS Parsing: <file://%C2%AD/p> against <about:blank>
-FAIL Parsing: <file://xn--/p> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
-PASS Parsing: <#link> against <https://example.org/##link>
-PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
-PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
-PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
-FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
-FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
-FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
-FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
-FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
-FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "wss://!\"$&'()*+,-.;=_`{}~/" but got "wss://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
-FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
-FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
-FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
-PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
-PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
-FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
-PASS Parsing: <abc:rootless> against <abc:path>
-FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
-FAIL Parsing: <http://1.2.3.4.5> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5" but got "http://1.2.3.4.5/"
-FAIL Parsing: <http://1.2.3.4.5.> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5." but got "http://1.2.3.4.5./"
-PASS Parsing: <http://0..0x300/> against <about:blank>
-PASS Parsing: <http://0..0x300./> against <about:blank>
-FAIL Parsing: <http://256.256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256" but got "http://256.256.256.256.256/"
-FAIL Parsing: <http://256.256.256.256.256.> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256." but got "http://256.256.256.256.256./"
-FAIL Parsing: <http://1.2.3.08> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08" but got "http://1.2.3.08/"
-FAIL Parsing: <http://1.2.3.08.> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08." but got "http://1.2.3.08./"
-FAIL Parsing: <http://1.2.3.09> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.09" but got "http://1.2.3.09/"
-FAIL Parsing: <http://09.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4" but got "http://09.2.3.4/"
-FAIL Parsing: <http://09.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4." but got "http://09.2.3.4./"
-FAIL Parsing: <http://01.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5" but got "http://01.2.3.4.5/"
-FAIL Parsing: <http://01.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5." but got "http://01.2.3.4.5./"
-FAIL Parsing: <http://0x100.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4" but got "http://0x100.2.3.4/"
-FAIL Parsing: <http://0x100.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4." but got "http://0x100.2.3.4./"
-FAIL Parsing: <http://0x1.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5" but got "http://0x1.2.3.4.5/"
-FAIL Parsing: <http://0x1.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5." but got "http://0x1.2.3.4.5./"
-FAIL Parsing: <http://foo.1.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4" but got "http://foo.1.2.3.4/"
-FAIL Parsing: <http://foo.1.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4." but got "http://foo.1.2.3.4./"
-FAIL Parsing: <http://foo.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4" but got "http://foo.2.3.4/"
-FAIL Parsing: <http://foo.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4." but got "http://foo.2.3.4./"
-FAIL Parsing: <http://foo.09> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09" but got "http://foo.09/"
-FAIL Parsing: <http://foo.09.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09." but got "http://foo.09./"
-FAIL Parsing: <http://foo.0x4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4" but got "http://foo.0x4/"
-FAIL Parsing: <http://foo.0x4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4." but got "http://foo.0x4./"
-PASS Parsing: <http://foo.09..> against <about:blank>
-PASS Parsing: <http://0999999999999999999/> against <about:blank>
-FAIL Parsing: <http://foo.0x> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x" but got "http://foo.0x/"
-FAIL Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123" but got "http://foo.0xfffffffffffffffffface123/"
-FAIL Parsing: <http://💩.123/> against <about:blank> assert_equals: failure should set href to input expected "http://💩.123/" but got "http://xn--ls8h.123/"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-origin-expected.txt
deleted file mode 100644
index 0353e737..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/url/a-element-origin-expected.txt
+++ /dev/null
@@ -1,342 +0,0 @@
-This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Loading data…
-PASS Parsing origin: <http://example	.
-org> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
-PASS Parsing origin: <https://test:@test> against <about:blank>
-PASS Parsing origin: <https://:@test> against <about:blank>
-PASS Parsing origin: <non-special://test:@test/x> against <about:blank>
-PASS Parsing origin: <non-special://:@test/x> against <about:blank>
-PASS Parsing origin: <http:foo.com> against <http://example.org/foo/bar>
-PASS Parsing origin: <	   :foo.com   
-> against <http://example.org/foo/bar>
-PASS Parsing origin: < foo.com  > against <http://example.org/foo/bar>
-PASS Parsing origin: <a:	 foo.com> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:0/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:00000000000000/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://f:
-/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <> against <http://example.org/foo/bar>
-PASS Parsing origin: <  	> against <http://example.org/foo/bar>
-PASS Parsing origin: <:foo.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <:foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <:> against <http://example.org/foo/bar>
-PASS Parsing origin: <:a> against <http://example.org/foo/bar>
-PASS Parsing origin: <:/> against <http://example.org/foo/bar>
-PASS Parsing origin: <:\> against <http://example.org/foo/bar>
-PASS Parsing origin: <:#> against <http://example.org/foo/bar>
-PASS Parsing origin: <#> against <http://example.org/foo/bar>
-PASS Parsing origin: <#/> against <http://example.org/foo/bar>
-PASS Parsing origin: <#\> against <http://example.org/foo/bar>
-PASS Parsing origin: <#;?> against <http://example.org/foo/bar>
-PASS Parsing origin: <?> against <http://example.org/foo/bar>
-PASS Parsing origin: </> against <http://example.org/foo/bar>
-PASS Parsing origin: <:23> against <http://example.org/foo/bar>
-PASS Parsing origin: </:23> against <http://example.org/foo/bar>
-PASS Parsing origin: <\x> against <http://example.org/foo/bar>
-PASS Parsing origin: <\\x\hello> against <http://example.org/foo/bar>
-PASS Parsing origin: <::> against <http://example.org/foo/bar>
-PASS Parsing origin: <::23> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://a:b@c:29/d> against <http://example.org/foo/bar>
-PASS Parsing origin: <http::@c:29> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://::@c@d:2> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo.com:b@d/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo.com/\@> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:\\foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:/bar.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://///////> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo://///////bar.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <foo:////://///> against <http://example.org/foo/bar>
-PASS Parsing origin: <c:/foo> against <http://example.org/foo/bar>
-PASS Parsing origin: <//foo/bar> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
-PASS Parsing origin: <[61:24:74]:98> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:[61:27]/:foo> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[2001::1]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
-PASS Parsing origin: <http://[2001::1]:80> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftp:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <https:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftps:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <gopher:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ws:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <wss:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <javascript:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <mailto:/example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftp:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <https:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <madeupscheme:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ftps:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <gopher:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <ws:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <wss:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <javascript:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: <mailto:example.com/> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/b/c> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/ /c> against <http://example.org/foo/bar>
-PASS Parsing origin: </a%2fc> against <http://example.org/foo/bar>
-PASS Parsing origin: </a/%2f/c> against <http://example.org/foo/bar>
-PASS Parsing origin: <#β> against <http://example.org/foo/bar>
-PASS Parsing origin: <data:text/html,test#test> against <http://example.org/foo/bar>
-PASS Parsing origin: <tel:1234567890> against <http://example.org/foo/bar>
-PASS Parsing origin: <ssh://example.com/foo/bar.git> against <http://example.org/>
-PASS Parsing origin: <http://example.com/././foo> against <about:blank>
-PASS Parsing origin: <http://example.com/./.foo> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/.> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/./> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/..bar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../ton> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/../../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/../../../ton> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e%2> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
-PASS Parsing origin: <http://example.com////../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar//../..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo/bar//..> against <about:blank>
-PASS Parsing origin: <http://example.com/foo> against <about:blank>
-PASS Parsing origin: <http://example.com/%20foo> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2zbar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%2©zbar> against <about:blank>
-PASS Parsing origin: <http://example.com/foo%41%7a> against <about:blank>
-PASS Parsing origin: <http://example.com/foo	‘%91> against <about:blank>
-FAIL Parsing origin: <http://example.com/foo%00%51> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
-PASS Parsing origin: <http://example.com/(%28:%3A%29)> against <about:blank>
-PASS Parsing origin: <http://example.com/%3A%3a%3C%3c> against <about:blank>
-PASS Parsing origin: <http://example.com/foo	bar> against <about:blank>
-PASS Parsing origin: <http://example.com\\foo\\bar> against <about:blank>
-PASS Parsing origin: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
-PASS Parsing origin: <http://example.com/@asdf%40> against <about:blank>
-PASS Parsing origin: <http://example.com/你好你好> against <about:blank>
-PASS Parsing origin: <http://example.com/‥/foo> against <about:blank>
-PASS Parsing origin: <http://example.com//foo> against <about:blank>
-PASS Parsing origin: <http://example.com/‮/foo/‭/bar> against <about:blank>
-PASS Parsing origin: <http://www.google.com/foo?bar=baz#> against <about:blank>
-PASS Parsing origin: <http://www.google.com/foo?bar=baz# »> against <about:blank>
-PASS Parsing origin: <data:test# »> against <about:blank>
-PASS Parsing origin: <http://www.google.com> against <about:blank>
-PASS Parsing origin: <http://192.0x00A80001> against <about:blank>
-PASS Parsing origin: <http://www/foo%2Ehtml> against <about:blank>
-PASS Parsing origin: <http://www/foo/%2E/html> against <about:blank>
-PASS Parsing origin: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
-PASS Parsing origin: <http:\\www.google.com\foo> against <about:blank>
-PASS Parsing origin: <http://foo:80/> against <about:blank>
-PASS Parsing origin: <http://foo:81/> against <about:blank>
-PASS Parsing origin: <httpa://foo:80/> against <about:blank>
-PASS Parsing origin: <https://foo:443/> against <about:blank>
-PASS Parsing origin: <https://foo:80/> against <about:blank>
-PASS Parsing origin: <ftp://foo:21/> against <about:blank>
-PASS Parsing origin: <ftp://foo:80/> against <about:blank>
-PASS Parsing origin: <gopher://foo:70/> against <about:blank>
-PASS Parsing origin: <gopher://foo:443/> against <about:blank>
-PASS Parsing origin: <ws://foo:80/> against <about:blank>
-PASS Parsing origin: <ws://foo:81/> against <about:blank>
-PASS Parsing origin: <ws://foo:443/> against <about:blank>
-PASS Parsing origin: <ws://foo:815/> against <about:blank>
-PASS Parsing origin: <wss://foo:80/> against <about:blank>
-PASS Parsing origin: <wss://foo:81/> against <about:blank>
-PASS Parsing origin: <wss://foo:443/> against <about:blank>
-PASS Parsing origin: <wss://foo:815/> against <about:blank>
-PASS Parsing origin: <http:/example.com/> against <about:blank>
-PASS Parsing origin: <ftp:/example.com/> against <about:blank>
-PASS Parsing origin: <https:/example.com/> against <about:blank>
-PASS Parsing origin: <madeupscheme:/example.com/> against <about:blank>
-PASS Parsing origin: <ftps:/example.com/> against <about:blank>
-PASS Parsing origin: <gopher:/example.com/> against <about:blank>
-PASS Parsing origin: <ws:/example.com/> against <about:blank>
-PASS Parsing origin: <wss:/example.com/> against <about:blank>
-PASS Parsing origin: <data:/example.com/> against <about:blank>
-PASS Parsing origin: <javascript:/example.com/> against <about:blank>
-PASS Parsing origin: <mailto:/example.com/> against <about:blank>
-PASS Parsing origin: <http:example.com/> against <about:blank>
-PASS Parsing origin: <ftp:example.com/> against <about:blank>
-PASS Parsing origin: <https:example.com/> against <about:blank>
-PASS Parsing origin: <madeupscheme:example.com/> against <about:blank>
-PASS Parsing origin: <ftps:example.com/> against <about:blank>
-PASS Parsing origin: <gopher:example.com/> against <about:blank>
-PASS Parsing origin: <ws:example.com/> against <about:blank>
-PASS Parsing origin: <wss:example.com/> against <about:blank>
-PASS Parsing origin: <data:example.com/> against <about:blank>
-PASS Parsing origin: <javascript:example.com/> against <about:blank>
-PASS Parsing origin: <mailto:example.com/> against <about:blank>
-PASS Parsing origin: <http:@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/@www.example.com> against <about:blank>
-PASS Parsing origin: <http://@www.example.com> against <about:blank>
-PASS Parsing origin: <http:a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://a:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://@pple.com> against <about:blank>
-PASS Parsing origin: <http::b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http://:b@www.example.com> against <about:blank>
-PASS Parsing origin: <http:a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http:/a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http://a:@www.example.com> against <about:blank>
-PASS Parsing origin: <http://www.@pple.com> against <about:blank>
-PASS Parsing origin: <http://:@www.example.com> against <about:blank>
-PASS Parsing origin: </> against <http://www.example.com/test>
-PASS Parsing origin: </test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <.> against <http://www.example.com/test>
-PASS Parsing origin: <..> against <http://www.example.com/test>
-PASS Parsing origin: <test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <./test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../aaa/test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <../../test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <中/test.txt> against <http://www.example.com/test>
-PASS Parsing origin: <http://www.example2.com> against <http://www.example.com/test>
-PASS Parsing origin: <//www.example2.com> against <http://www.example.com/test>
-PASS Parsing origin: <http://ExAmPlE.CoM> against <http://other.com/>
-PASS Parsing origin: <http://GOO​⁠goo.com> against <http://other.com/>
-PASS Parsing origin: <\0 http://example.com/ \r > against <about:blank>
-PASS Parsing origin: <http://www.foo。bar.com> against <http://other.com/>
-PASS Parsing origin: <https://x/�?�#�> against <about:blank>
-PASS Parsing origin: <http://Go.com> against <http://other.com/>
-PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
-PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
-PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
-PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
-PASS Parsing origin: <http://0Xc0.0250.01> against <http://other.com/>
-PASS Parsing origin: <http://./> against <about:blank>
-PASS Parsing origin: <http://../> against <about:blank>
-PASS Parsing origin: <http://foo:💩@example.com/bar> against <http://other.com/>
-PASS Parsing origin: <#> against <test:test>
-PASS Parsing origin: <#x> against <mailto:x@x.com>
-PASS Parsing origin: <#x> against <data:,>
-PASS Parsing origin: <#x> against <about:blank>
-PASS Parsing origin: <#> against <test:test?test>
-PASS Parsing origin: <https://@test@test@example:800/> against <http://doesnotmatter/>
-PASS Parsing origin: <https://@@@example> against <http://doesnotmatter/>
-PASS Parsing origin: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
-PASS Parsing origin: <http://host/?'> against <about:blank>
-PASS Parsing origin: <notspecial://host/?'> against <about:blank>
-PASS Parsing origin: </some/path> against <http://user@example.org/smth>
-PASS Parsing origin: <> against <http://user:pass@example.org:21/smth>
-PASS Parsing origin: </some/path> against <http://user:pass@example.org:21/smth>
-PASS Parsing origin: <i> against <sc:/pa/pa>
-PASS Parsing origin: <i> against <sc://ho/pa>
-PASS Parsing origin: <i> against <sc:///pa/pa>
-PASS Parsing origin: <../i> against <sc:/pa/pa>
-PASS Parsing origin: <../i> against <sc://ho/pa>
-PASS Parsing origin: <../i> against <sc:///pa/pa>
-PASS Parsing origin: </i> against <sc:/pa/pa>
-PASS Parsing origin: </i> against <sc://ho/pa>
-PASS Parsing origin: </i> against <sc:///pa/pa>
-PASS Parsing origin: <?i> against <sc:/pa/pa>
-PASS Parsing origin: <?i> against <sc://ho/pa>
-PASS Parsing origin: <?i> against <sc:///pa/pa>
-PASS Parsing origin: <#i> against <sc:sd>
-PASS Parsing origin: <#i> against <sc:sd/sd>
-PASS Parsing origin: <#i> against <sc:/pa/pa>
-PASS Parsing origin: <#i> against <sc://ho/pa>
-PASS Parsing origin: <#i> against <sc:///pa/pa>
-PASS Parsing origin: <about:/../> against <about:blank>
-PASS Parsing origin: <data:/../> against <about:blank>
-PASS Parsing origin: <javascript:/../> against <about:blank>
-PASS Parsing origin: <mailto:/../> against <about:blank>
-PASS Parsing origin: <sc://ñ.test/> against <about:blank>
-PASS Parsing origin: <x> against <sc://ñ>
-PASS Parsing origin: <sc:\../> against <about:blank>
-PASS Parsing origin: <sc::a@example.net> against <about:blank>
-PASS Parsing origin: <wow:%NBD> against <about:blank>
-PASS Parsing origin: <wow:%1G> against <about:blank>
-PASS Parsing origin: <wow:￿> against <about:blank>
-FAIL Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
-FAIL Parsing origin: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "http://\x1f!\"$&'()*+,-.;=_`{}~" but got "null"
-PASS Parsing origin: <sc://!"$&'()*+,-.;=_`{}~/> against <about:blank>
-PASS Parsing origin: <ftp://%e2%98%83> against <about:blank>
-PASS Parsing origin: <https://%e2%98%83> against <about:blank>
-PASS Parsing origin: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
-PASS Parsing origin: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
-PASS Parsing origin: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
-PASS Parsing origin: <h	t
-t\rp://h	o
-s\rt:9	0
-0\r0/p	a
-t\rh?q	u
-e\rry#f	r
-a\rg> against <about:blank>
-PASS Parsing origin: <?a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing origin: <??a=b&c=d> against <http://example.org/foo/bar>
-PASS Parsing origin: <http:> against <http://example.org/foo/bar>
-PASS Parsing origin: <sc:> against <https://example.org/foo/bar>
-PASS Parsing origin: <http://foo.bar/baz?qux#foobar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
-PASS Parsing origin: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
-PASS Parsing origin: <http://1.2.3.4/> against <http://other.com/>
-PASS Parsing origin: <http://1.2.3.4./> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257.> against <http://other.com/>
-PASS Parsing origin: <http://192.168.257.com> against <http://other.com/>
-PASS Parsing origin: <http://256> against <http://other.com/>
-PASS Parsing origin: <http://256.com> against <http://other.com/>
-PASS Parsing origin: <http://999999999> against <http://other.com/>
-PASS Parsing origin: <http://999999999.> against <http://other.com/>
-PASS Parsing origin: <http://999999999.com> against <http://other.com/>
-PASS Parsing origin: <http://10000000000.com> against <http://other.com/>
-PASS Parsing origin: <http://4294967295> against <http://other.com/>
-PASS Parsing origin: <http://0xffffffff> against <http://other.com/>
-PASS Parsing origin: <https://0x.0x.0> against <about:blank>
-PASS Parsing origin: <asdf://%43%7C/> against <about:blank>
-PASS Parsing origin: <http://[1:0::]> against <http://example.net/>
-PASS Parsing origin: <sc://ñ> against <about:blank>
-PASS Parsing origin: <sc://ñ?x> against <about:blank>
-PASS Parsing origin: <sc://ñ#x> against <about:blank>
-PASS Parsing origin: <#x> against <sc://ñ>
-PASS Parsing origin: <?x> against <sc://ñ>
-PASS Parsing origin: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
-PASS Parsing origin: <telnet://user:pass@foobar.com:23/> against <about:blank>
-PASS Parsing origin: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
-PASS Parsing origin: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
-PASS Parsing origin: <rsync://foo@host:911/sup> against <about:blank>
-PASS Parsing origin: <git://github.com/foo/bar.git> against <about:blank>
-PASS Parsing origin: <irc://myserver.com:6999/channel?passwd> against <about:blank>
-PASS Parsing origin: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
-PASS Parsing origin: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
-PASS Parsing origin: <git+https://github.com/foo/bar> against <about:blank>
-PASS Parsing origin: <urn:ietf:rfc:2648> against <about:blank>
-PASS Parsing origin: <tag:joe@example.org,2001:foo/bar> against <about:blank>
-PASS Parsing origin: <blob:https://example.com:443/> against <about:blank>
-PASS Parsing origin: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
-PASS Parsing origin: <blob:> against <about:blank>
-PASS Parsing origin: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
-PASS Parsing origin: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
-PASS Parsing origin: <https://user:pass[@foo/bar> against <http://example.org>
-PASS Parsing origin: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
-PASS Parsing origin: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
-FAIL Parsing origin: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "wss://!\"$&'()*+,-.;=_`{}~" but got "null"
-PASS Parsing origin: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-PASS Parsing origin: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
new file mode 100644
index 0000000..aa5c600
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
@@ -0,0 +1,124 @@
+This is a testharness.js-based test.
+Found 120 tests; 112 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+FAIL CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+FAIL CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-writing-modes/alt-display-vertical-001-manual-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-writing-modes/alt-display-vertical-001-manual-expected.png
new file mode 100644
index 0000000..65b2e569
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/css/css-writing-modes/alt-display-vertical-001-manual-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold-expected.txt
new file mode 100644
index 0000000..bb885d8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold-expected.txt
@@ -0,0 +1,110 @@
+This is a testharness.js-based test.
+Found 106 tests; 103 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS # AUDIT TASK RUNNER STARTED.
+PASS Executing "cancelTime"
+PASS Executing "linear"
+PASS Executing "exponential"
+PASS Executing "setTarget"
+PASS Executing "setValueCurve"
+PASS Executing "setValueCurve after end"
+PASS Executing "initial setTarget"
+PASS Executing "post cancel: Linear"
+PASS Executing "post cancel: Exponential"
+PASS Executing "post cancel: ValueCurve"
+PASS Executing "post cancel: setTarget"
+PASS Executing "post cancel: setValue"
+PASS Executing "cancel future setTarget"
+PASS Executing "cancel setTarget now"
+PASS Executing "cancel future setValueCurve"
+PASS Executing "cancel setValueCurve now"
+PASS Executing "linear, cancel, linear, cancel, linear"
+PASS Audit report
+PASS > [cancelTime] Test Invalid Values
+PASS   cancelAndHoldAtTime(-1) threw RangeError: "Failed to execute 'cancelAndHoldAtTime' on 'AudioParam': Time must be a finite non-negative number: -1".
+PASS   cancelAndHoldAtTime(NaN) threw TypeError: "Failed to execute 'cancelAndHoldAtTime' on 'AudioParam': The provided double value is non-finite.".
+PASS   cancelAndHoldAtTime(Infinity) threw TypeError: "Failed to execute 'cancelAndHoldAtTime' on 'AudioParam': The provided double value is non-finite.".
+PASS < [cancelTime] All assertions passed. (total 3 assertions)
+PASS > [linear] Cancel linearRampToValueAtTime
+PASS   linearRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000059605,"relativeThreshold":0}.
+PASS   Cancelling linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS < [linear] All assertions passed. (total 3 assertions)
+PASS > [exponential] Cancel exponentialRampAtTime
+PASS   exponentialRampToValue(0.001, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling exponentialRampToValue(0.001, 0.5) at time 0.25 contains only the constant 0.033932220190763474.
+PASS   Expected value for cancelling exponentialRampToValue(0.001, 0.5) at time 0.25 is 0.033932216465473175 within an error of 0.0000018664.
+PASS < [exponential] All assertions passed. (total 3 assertions)
+PASS > [setTarget] Cancel setTargetAtTime
+PASS   setTargetAtTime(0, 0.01, 0.05) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS   Cancelling setTargetAtTime(0, 0.01, 0.05) at time 0.25 contains only the constant 0.008229752071201801.
+FAIL X Expected value for cancelling setTargetAtTime(0, 0.01, 0.05) at time 0.25 is not close to 0.008229747414588928 within a relative error of 4.5267e-7 (RelErr=5.658269492964734e-7). Got 0.008229752071201801. assert_true: expected true got false
+FAIL < [setTarget] 1 out of 3 assertions were failed. assert_true: expected true got false
+PASS > [setValueCurve] Cancel setValueCurveAtTime
+PASS   setValueCurveAtTime([1,0], 0.01, 0.49) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS   Cancelling setValueCurveAtTime([1,0], 0.01, 0.49) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling setValueCurveAtTime([1,0], 0.01, 0.49) at time 0.25 is 0.510204081632653 within an error of 9.5368e-9.
+PASS < [setValueCurve] All assertions passed. (total 3 assertions)
+PASS > [setValueCurve after end] Cancel setValueCurveAtTime after the end
+PASS   setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS   Cancelling setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) at time 0.25 contains only the constant 0.
+PASS   Expected value for cancelling setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) at time 0.25 is 0 within an error of 0.
+PASS < [setValueCurve after end] All assertions passed. (total 3 assertions)
+PASS > [initial setTarget] Cancel with initial setTargetAtTime
+PASS   setTargetAtTime(0, 0.01, 0.1) up to time 0.25 equals [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS   Cancelling setTargetAtTime(0, 0.01, 0.1) at time 0.25 contains only the constant 0.09071767330169678.
+PASS   Expected value for cancelling setTargetAtTime(0, 0.01, 0.1) at time 0.25 is 0.09071795642375946 within an error of 0.000003121.
+PASS < [initial setTarget] All assertions passed. (total 3 assertions)
+PASS > [post cancel: Linear] LinearRamp after cancelling
+PASS   Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS   Post linearRamp(2, 0.375) equals [0.5102040767669678,0.510452389717102,0.5107007026672363,0.5109489560127258,0.5111972689628601,0.5114455819129944,0.5116938948631287,0.5119421482086182,0.5121904611587524,0.5124387741088867,0.512687087059021,0.5129353404045105,0.5131836533546448,0.513431966304779,0.5136802792549133,0.5139285922050476...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: Linear] All assertions passed. (total 4 assertions)
+PASS > [post cancel: Exponential] ExponentialRamp after cancelling
+PASS   Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS   Post exponentialRamp(2, 0.375) equals [0.5102040767669678,0.510320246219635,0.5104364156723022,0.5105526447296143,0.5106688737869263,0.5107851624488831,0.5109014511108398,0.5110177993774414,0.511134147644043,0.5112505555152893,0.5113669633865356,0.511483371257782,0.5115998387336731,0.5117163062095642,0.5118328332901001,0.511949360370636...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: Exponential] All assertions passed. (total 4 assertions)
+PASS > [post cancel: ValueCurve] 
+PASS   Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS   Post setValueCurve([0.125,2], 0.375, 0.125) equals [0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS < [post cancel: ValueCurve] All assertions passed. (total 4 assertions)
+PASS > [post cancel: setTarget] 
+PASS   Post cancel setTargetAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling Post cancel setTargetAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling Post cancel setTargetAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS   Post setTargetAtTime(0.125, 0.375, 0.1) equals [0.5102040767669678,0.5101238489151001,0.5100436210632324,0.5099633932113647,0.5098832249641418,0.509803056716919,0.509722888469696,0.5096427202224731,0.509562611579895,0.5094825029373169,0.5094023942947388,0.5093223452568054,0.5092422962188721,0.5091622471809387,0.5090821981430054,0.5090022087097168...] with an element-wise tolerance of {"absoluteThreshold":0.000084037,"relativeThreshold":0}.
+PASS < [post cancel: setTarget] All assertions passed. (total 4 assertions)
+PASS > [post cancel: setValue] 
+PASS   Post cancel setValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling Post cancel setValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling Post cancel setValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS   Post setValueAtTime(0.125, 0.375) equals [0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: setValue] All assertions passed. (total 4 assertions)
+PASS > [cancel future setTarget] 
+PASS   After cancelling future setTarget event, output contains only the constant 0.5.
+PASS < [cancel future setTarget] All assertions passed. (total 1 assertions)
+PASS > [cancel setTarget now] 
+PASS   After cancelling setTarget event starting now, output contains only the constant 0.5.
+PASS < [cancel setTarget now] All assertions passed. (total 1 assertions)
+PASS > [cancel future setValueCurve] 
+PASS   After cancelling future setValueCurve event, output contains only the constant 0.5.
+PASS < [cancel future setValueCurve] All assertions passed. (total 1 assertions)
+PASS > [cancel setValueCurve now] 
+PASS   After cancelling current setValueCurve event starting now, output contains only the constant 0.5.
+PASS < [cancel setValueCurve now] All assertions passed. (total 1 assertions)
+PASS > [linear, cancel, linear, cancel, linear] Schedules 3 linear ramps, cancelling 2 of them, so that we end up with 2 cancel events next to each other
+PASS   1st linearRamp: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.0020833334419876337,0.004166666883975267,0.0062500000931322575,0.008333333767950535,0.010416666977107525,0.012500000186264515,0.014583333395421505,0.01666666753590107,0.01875000074505806,0.02083333395421505,0.02291666716337204,0.02500000037252903,0.02708333358168602,0.02916666679084301,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS   Cancelling 1st linearRamp: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS   Expected value for cancelling 1st linearRamp: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.
+PASS   2nd linearRamp(2, 0.5) equals [0.5102040767669678,0.5103282332420349,0.510452389717102,0.5105765461921692,0.5107007026672363,0.5108247995376587,0.5109489560127258,0.511073112487793,0.5111972689628601,0.5113214254379272,0.5114455819129944,0.5115697383880615,0.5116938948631287,0.5118180513381958,0.5119421482086182,0.5120663046836853...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS   Cancelling 2nd linearRamp(2, 0.5) at time 0.375 contains only the constant 1.2551020383834839.
+PASS   Expected value for cancelling 2nd linearRamp(2, 0.5) at time 0.375 is 1.2551020383834839 within an error of 0.
+PASS   3rd linearRamp(0, 0.5) equals [1.2551020383834839,1.2548928260803223,1.2546836137771606,1.2544745206832886,1.254265308380127,1.2540560960769653,1.2538468837738037,1.2536377906799316,1.25342857837677,1.2532193660736084,1.2530101537704468,1.2528010606765747,1.252591848373413,1.2523826360702515,1.2521734237670898,1.2519643306732178...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [linear, cancel, linear, cancel, linear] All assertions passed. (total 7 assertions)
+FAIL # AUDIT TASK RUNNER FINISHED: 1 out of 17 tasks were failed. assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-dynamics-compressor-connections-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-dynamics-compressor-connections-expected.txt
new file mode 100644
index 0000000..7c30828
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-dynamics-compressor-connections-expected.txt
@@ -0,0 +1,79 @@
+This is a testharness.js-based test.
+Found 63 tests; 60 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS # AUDIT TASK RUNNER STARTED.
+PASS Executing "attack"
+PASS Executing "knee"
+PASS Executing "ratio"
+PASS Executing "release"
+PASS Executing "threshold"
+PASS Audit report
+PASS > [attack] Dynamics compressor attack
+PASS   attack: refNode = new DynamicsCompressorNode(context) did not throw an exception.
+PASS   attack: tstNode = new DynamicsCompressorNode(context, {"attack":0}) did not throw an exception.
+PASS   attack: refNode[attack].setValueAtTime(refNode[attack].minValue, 0) did not throw an exception.
+PASS   attack: refNode[attack].linearRampToValueAtTime(refNode[attack].minValue, 0.25) did not throw an exception.
+PASS   attack: mod = new ConstantSourceNode(context, {offset: 0}) did not throw an exception.
+PASS   attack: mod.offset.setValueAtTime(0, 0) did not throw an exception.
+PASS   attack: mod.offset.linearRampToValueAtTime(1, 0.25) did not throw an exception.
+PASS   attack: mod.connect(tstNode[attack]) did not throw an exception.
+PASS   k-rate attack AudioParam with input equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [attack] All assertions passed. (total 9 assertions)
+PASS > [knee] Dynamics compressor knee
+PASS   knee: refNode = new DynamicsCompressorNode(context) did not throw an exception.
+PASS   knee: tstNode = new DynamicsCompressorNode(context, {"knee":0}) did not throw an exception.
+PASS   knee: refNode[knee].setValueAtTime(refNode[knee].minValue, 0) did not throw an exception.
+PASS   knee: refNode[knee].linearRampToValueAtTime(refNode[knee].minValue, 0.25) did not throw an exception.
+PASS   knee: mod = new ConstantSourceNode(context, {offset: 0}) did not throw an exception.
+PASS   knee: mod.offset.setValueAtTime(0, 0) did not throw an exception.
+PASS   knee: mod.offset.linearRampToValueAtTime(40, 0.25) did not throw an exception.
+PASS   knee: mod.connect(tstNode[knee]) did not throw an exception.
+PASS   k-rate knee AudioParam with input equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [knee] All assertions passed. (total 9 assertions)
+PASS > [ratio] Dynamics compressor ratio
+PASS   ratio: refNode = new DynamicsCompressorNode(context) did not throw an exception.
+PASS   ratio: tstNode = new DynamicsCompressorNode(context, {"ratio":1}) did not throw an exception.
+PASS   ratio: refNode[ratio].setValueAtTime(refNode[ratio].minValue, 0) did not throw an exception.
+PASS   ratio: refNode[ratio].linearRampToValueAtTime(refNode[ratio].minValue, 0.25) did not throw an exception.
+PASS   ratio: mod = new ConstantSourceNode(context, {offset: 0}) did not throw an exception.
+PASS   ratio: mod.offset.setValueAtTime(0, 0) did not throw an exception.
+PASS   ratio: mod.offset.linearRampToValueAtTime(19, 0.25) did not throw an exception.
+PASS   ratio: mod.connect(tstNode[ratio]) did not throw an exception.
+PASS   k-rate ratio AudioParam with input equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [ratio] All assertions passed. (total 9 assertions)
+PASS > [release] Dynamics compressor release
+PASS   release: refNode = new DynamicsCompressorNode(context) did not throw an exception.
+PASS   release: tstNode = new DynamicsCompressorNode(context, {"release":0}) did not throw an exception.
+PASS   release: refNode[release].setValueAtTime(refNode[release].minValue, 0) did not throw an exception.
+PASS   release: refNode[release].linearRampToValueAtTime(refNode[release].minValue, 0.25) did not throw an exception.
+PASS   release: mod = new ConstantSourceNode(context, {offset: 0}) did not throw an exception.
+PASS   release: mod.offset.setValueAtTime(0, 0) did not throw an exception.
+PASS   release: mod.offset.linearRampToValueAtTime(1, 0.25) did not throw an exception.
+PASS   release: mod.connect(tstNode[release]) did not throw an exception.
+PASS   k-rate release AudioParam with input equals [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [release] All assertions passed. (total 9 assertions)
+PASS > [threshold] Dynamics compressor threshold
+PASS   threshold: refNode = new DynamicsCompressorNode(context) did not throw an exception.
+PASS   threshold: tstNode = new DynamicsCompressorNode(context, {"threshold":-100}) did not throw an exception.
+PASS   threshold: refNode[threshold].setValueAtTime(refNode[threshold].minValue, 0) did not throw an exception.
+PASS   threshold: refNode[threshold].linearRampToValueAtTime(refNode[threshold].minValue, 0.25) did not throw an exception.
+PASS   threshold: mod = new ConstantSourceNode(context, {offset: 0}) did not throw an exception.
+PASS   threshold: mod.offset.setValueAtTime(0, 0) did not throw an exception.
+PASS   threshold: mod.offset.linearRampToValueAtTime(100, 0.25) did not throw an exception.
+PASS   threshold: mod.connect(tstNode[threshold]) did not throw an exception.
+FAIL X k-rate threshold AudioParam with input does not equal [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+	Index	Actual			Expected		AbsError		RelError		Test threshold
+	[512]	4.0975883603096008e-3	4.0966668166220188e-3	9.2154368758201599e-7	2.2494963072000363e-4	0.0000000000000000e+0
+	[513]	4.9103251658380032e-3	4.9092206172645092e-3	1.1045485734939575e-6	2.2499469052369222e-4	0.0000000000000000e+0
+	[514]	5.8354442007839680e-3	5.8341315016150475e-3	1.3126991689205170e-6	2.2500335629341333e-4	0.0000000000000000e+0
+	[515]	6.1213369481265545e-3	6.1199599876999855e-3	1.3769604265689850e-6	2.2499500476088518e-4	0.0000000000000000e+0
+	[516]	7.3028206825256348e-3	7.3011782951653004e-3	1.6423873603343964e-6	2.2494826094329921e-4	0.0000000000000000e+0
+	...and 11483 more errors.
+	Max AbsError of 3.0715018510818481e-5 at index of 2523.
+	[2523]	3.1251750886440277e-2	3.1282465904951096e-2	3.0715018510818481e-5	9.8186052864704626e-4	0.0000000000000000e+0
+	Max RelError of 1.0166148481892119e-3 at index of 2450.
+	[2450]	-9.4820801168680191e-3	-9.4917295500636101e-3	9.6494331955909729e-6	1.0166148481892119e-3	0.0000000000000000e+0
+ assert_true: expected true got false
+FAIL < [threshold] 1 out of 9 assertions were failed. assert_true: expected true got false
+FAIL # AUDIT TASK RUNNER FINISHED: 1 out of 5 tasks were failed. assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/backface-visibility-interop/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/backface-visibility-interop/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
new file mode 100644
index 0000000..aa5c600
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/backface-visibility-interop/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
@@ -0,0 +1,124 @@
+This is a testharness.js-based test.
+Found 120 tests; 112 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+FAIL CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+FAIL CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/composite-relative-keyframes/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/composite-relative-keyframes/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
new file mode 100644
index 0000000..aa5c600
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/composite-relative-keyframes/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
@@ -0,0 +1,124 @@
+This is a testharness.js-based test.
+Found 120 tests; 112 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+FAIL CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+FAIL CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/threaded/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/threaded/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
new file mode 100644
index 0000000..aa5c600
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/threaded/external/wpt/css/css-transforms/animation/perspective-origin-interpolation-expected.txt
@@ -0,0 +1,124 @@
+This is a testharness.js-based test.
+Found 120 tests; 112 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (-0.3) should be [7px 33px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0) should be [10px 30px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.3) should be [13px 27px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (0.6) should be [16px 24px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from neutral to [20px 20px] at (1.5) should be [25px 15px]
+FAIL CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [initial] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (-0.3) should be [33px 7px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0) should be [30px 10px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.3) should be [27px 13px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (0.6) should be [24px 16px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [inherit] to [20px 20px] at (1.5) should be [15px 25px]
+FAIL CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS CSS Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+FAIL Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (-0.3) should be [26.5px 26.5px] assert_equals: expected "26.5px 26.5px " but got "26.48px 26.48px "
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0) should be [25px 25px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.3) should be [23.5px 23.5px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (0.6) should be [22px 22px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1) should be [20px 20px]
+PASS Web Animations: property <perspective-origin> from [unset] to [20px 20px] at (1.5) should be [17.5px 17.5px]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Transitions with transition: all: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS CSS Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (-0.3) should be [-30% 20%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0) should be [0% 50%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.3) should be [30% 80%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (0.6) should be [60% 110%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1) should be [100% 150%]
+PASS Web Animations: property <perspective-origin> from [0% 50%] to [100% 150%] at (1.5) should be [150% 200%]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/resource-timing/initiator-type/workers-expected.txt b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/resource-timing/initiator-type/workers-expected.txt
new file mode 100644
index 0000000..ebaffe2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/resource-timing/initiator-type/workers-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+FAIL The initiator type for classic worker must be 'other' promise_test: Unhandled rejection with value: object "Error: observe_entry: timeout"
+FAIL The initiator type for module worker must be 'other' promise_test: Unhandled rejection with value: object "Error: observe_entry: timeout"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/README.txt b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/README.txt
new file mode 100644
index 0000000..2289aa4
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/README.txt
@@ -0,0 +1 @@
+This suite runs web-locks tests with ThirdPartyStoragePartitioning enabled.
diff --git a/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt
new file mode 100644
index 0000000..a19e6d2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/web-locks/partitioned-web-locks.tentative.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+PASS WebLocks of an iframe under a 3rd-party site are partitioned
+Harness: the test ran to completion.
+
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index 7f8134bf..8240ae8 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -340,7 +340,8 @@
  * @typedef {{
  *   visible: boolean,
  *   icon: !chrome.accessibilityPrivate.DictationBubbleIconType,
- *   text: (string|undefined)
+ *   text: (string|undefined),
+ *   hints: (!Array<string>|undefined)
  * }}
  */
 chrome.accessibilityPrivate.DictationBubbleProperties;
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 9751c2eb..2cb75d6 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 50531fc68fa220f0ce7f178571493c8660a7688c
+Revision: 36ad57186286ac6511ce149fd51e1b52c2d3e2e4
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_ios.cc b/third_party/crashpad/crashpad/client/crashpad_client_ios.cc
index 467cc0cf..7c378ba7 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_ios.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_ios.cc
@@ -71,6 +71,13 @@
     if (!in_process_handler_.Initialize(
             database, url, annotations, system_data_) ||
         !InstallMachExceptionHandler() ||
+        // xnu turns hardware faults into Mach exceptions, so the only signal
+        // left to register is SIGABRT, which never starts off as a hardware
+        // fault. Installing a handler for other signals would lead to
+        // recording exceptions twice. As a consequence, Crashpad will not
+        // generate intermediate dumps for anything manually calling
+        // raise(SIG*). In practice, this doesn’t actually happen for crash
+        // signals that originate as hardware faults.
         !Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)) {
       LOG(ERROR) << "Unable to initialize Crashpad.";
       return false;
diff --git a/third_party/crashpad/crashpad/snapshot/BUILD.gn b/third_party/crashpad/crashpad/snapshot/BUILD.gn
index e3ad7f67..1a1d893 100644
--- a/third_party/crashpad/crashpad/snapshot/BUILD.gn
+++ b/third_party/crashpad/crashpad/snapshot/BUILD.gn
@@ -364,6 +364,7 @@
 
   if (crashpad_is_ios) {
     sources += [
+      "ios/memory_snapshot_ios_intermediate_dump_test.cc",
       "ios/process_snapshot_ios_intermediate_dump_test.cc",
       "mac/cpu_context_mac_test.cc",
     ]
diff --git a/third_party/crashpad/crashpad/snapshot/ios/memory_snapshot_ios_intermediate_dump_test.cc b/third_party/crashpad/crashpad/snapshot/ios/memory_snapshot_ios_intermediate_dump_test.cc
new file mode 100644
index 0000000..bbe2a88
--- /dev/null
+++ b/third_party/crashpad/crashpad/snapshot/ios/memory_snapshot_ios_intermediate_dump_test.cc
@@ -0,0 +1,161 @@
+// Copyright 2022 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "snapshot/ios/memory_snapshot_ios_intermediate_dump.h"
+
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using internal::MemorySnapshotIOSIntermediateDump;
+
+const vm_address_t kDefaultAddress = 0x1000;
+
+class ReadToString : public crashpad::MemorySnapshot::Delegate {
+ public:
+  const std::string& result() { return result_; }
+
+ private:
+  // MemorySnapshot::Delegate:
+  bool MemorySnapshotDelegateRead(void* data, size_t size) override {
+    result_ = std::string(reinterpret_cast<const char*>(data), size);
+    return true;
+  }
+
+  std::string result_;
+};
+
+std::unique_ptr<MemorySnapshotIOSIntermediateDump> CreateMemorySnapshot(
+    vm_address_t address,
+    std::vector<uint8_t>& data) {
+  auto memory = std::make_unique<MemorySnapshotIOSIntermediateDump>();
+  memory->Initialize(
+      address, reinterpret_cast<const vm_address_t>(data.data()), data.size());
+  return memory;
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, MergeSame) {
+  std::vector<uint8_t> data(10, 'a');
+  auto memory = CreateMemorySnapshot(kDefaultAddress, data);
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory->MergeWithOtherSnapshot(memory.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress);
+  EXPECT_EQ(merged->Size(), data.size());
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "aaaaaaaaaa");
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, MergeNoOverlap) {
+  std::vector<uint8_t> data1(10, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(10, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress + 10, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress);
+  EXPECT_EQ(merged->Size(), 20u);
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "aaaaaaaaaabbbbbbbbbb");
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, MergePartial) {
+  std::vector<uint8_t> data1(10, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(10, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress + 5, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress);
+  EXPECT_EQ(merged->Size(), 15u);
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "aaaaabbbbbbbbbb");
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, NoMerge) {
+  std::vector<uint8_t> data1(10, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(10, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress + 20, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged.get(), nullptr);
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, EnvelopeBiggerFirst) {
+  std::vector<uint8_t> data1(30, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(10, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress + 15, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress);
+  EXPECT_EQ(merged->Size(), data1.size());
+
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, EnvelopeBiggerSecond) {
+  std::vector<uint8_t> data1(10, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(20, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress);
+  EXPECT_EQ(merged->Size(), data2.size());
+
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "bbbbbbbbbbbbbbbbbbbb");
+}
+
+TEST(MemorySnapshotIOSIntermediateDumpTest, SmallerAddressSecond) {
+  std::vector<uint8_t> data1(10, 'a');
+  auto memory1 = CreateMemorySnapshot(kDefaultAddress, data1);
+
+  std::vector<uint8_t> data2(20, 'b');
+  auto memory2 = CreateMemorySnapshot(kDefaultAddress - 10, data2);
+
+  std::unique_ptr<const MemorySnapshot> merged(
+      memory1->MergeWithOtherSnapshot(memory2.get()));
+  EXPECT_EQ(merged->Address(), kDefaultAddress - 10);
+  EXPECT_EQ(merged->Size(), data2.size());
+  ReadToString delegate;
+  merged->Read(&delegate);
+  EXPECT_EQ(delegate.result(), "bbbbbbbbbbbbbbbbbbbb");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/test/ios/crash_type_xctest.mm b/third_party/crashpad/crashpad/test/ios/crash_type_xctest.mm
index 3613e22..1022d29 100644
--- a/third_party/crashpad/crashpad/test/ios/crash_type_xctest.mm
+++ b/third_party/crashpad/crashpad/test/ios/crash_type_xctest.mm
@@ -16,6 +16,7 @@
 #include <objc/runtime.h>
 
 #import "Service/Sources/EDOClientService.h"
+#include "build/build_config.h"
 #import "test/ios/host/cptest_shared_object.h"
 #include "util/mach/exception_types.h"
 #include "util/mach/mach_extensions.h"
@@ -33,15 +34,8 @@
 @implementation CPTestTestCase
 
 + (void)setUp {
-  // Swizzle away the handleCrashUnderSymbol callback.  Without this, any time
-  // the host app is intentionally crashed, the test is immediately failed.
-  SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:");
-  SEL swizzledSelector = @selector(handleCrashUnderSymbol:);
-  Method originalMethod = class_getInstanceMethod(
-      objc_getClass("XCUIApplicationImpl"), originalSelector);
-  Method swizzledMethod =
-      class_getInstanceMethod([self class], swizzledSelector);
-  method_exchangeImplementations(originalMethod, swizzledMethod);
+  [CPTestTestCase swizzleHandleCrashUnderSymbol];
+  [CPTestTestCase swizleMayTerminateOutOfBandWithoutCrashReport];
 
   // Override EDO default error handler.  Without this, the default EDO error
   // handler will throw an error and fail the test.
@@ -50,12 +44,43 @@
   });
 }
 
+// Swizzle away the -[XCUIApplicationImpl handleCrashUnderSymbol:] callback.
+// Without this, any time the host app is intentionally crashed, the test is
+// immediately failed.
++ (void)swizzleHandleCrashUnderSymbol {
+  SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:");
+  SEL swizzledSelector = @selector(handleCrashUnderSymbol:);
+  Method originalMethod = class_getInstanceMethod(
+      objc_getClass("XCUIApplicationImpl"), originalSelector);
+  Method swizzledMethod =
+      class_getInstanceMethod([self class], swizzledSelector);
+  method_exchangeImplementations(originalMethod, swizzledMethod);
+}
+
+// Swizzle away the time consuming 'Checking for crash reports corresponding to'
+// from -[XCUIApplicationProcess swizleMayTerminateOutOfBandWithoutCrashReport]
+// that is unnecessary for these tests.
++ (void)swizleMayTerminateOutOfBandWithoutCrashReport {
+  SEL originalSelector =
+      NSSelectorFromString(@"mayTerminateOutOfBandWithoutCrashReport");
+  SEL swizzledSelector = @selector(mayTerminateOutOfBandWithoutCrashReport);
+  Method originalMethod = class_getInstanceMethod(
+      objc_getClass("XCUIApplicationProcess"), originalSelector);
+  Method swizzledMethod =
+      class_getInstanceMethod([self class], swizzledSelector);
+  method_exchangeImplementations(originalMethod, swizzledMethod);
+}
+
 // This gets called after tearDown, so there's no straightforward way to
 // test that this is called. However, not swizzling this out will cause every
 // crashing test to fail.
 - (void)handleCrashUnderSymbol:(id)arg1 {
 }
 
+- (BOOL)mayTerminateOutOfBandWithoutCrashReport {
+  return YES;
+}
+
 - (void)setUp {
   app_ = [[XCUIApplication alloc] init];
   [app_ launch];
@@ -85,19 +110,6 @@
   XCTAssertEqualObjects(result, @"crashpad");
 }
 
-- (void)testSegv {
-  [rootObject_ crashSegv];
-#if defined(NDEBUG)
-#if TARGET_OS_SIMULATOR
-  [self verifyCrashReportException:EXC_BAD_INSTRUCTION];
-#else
-  [self verifyCrashReportException:EXC_BREAKPOINT];
-#endif
-#else
-  [self verifyCrashReportException:EXC_BAD_ACCESS];
-#endif
-}
-
 - (void)testKillAbort {
   [rootObject_ crashKillAbort];
   [self verifyCrashReportException:EXC_SOFT_SIGNAL];
@@ -108,10 +120,12 @@
 
 - (void)testTrap {
   [rootObject_ crashTrap];
-#if TARGET_OS_SIMULATOR
+#if defined(ARCH_CPU_X86_64)
   [self verifyCrashReportException:EXC_BAD_INSTRUCTION];
-#else
+#elif defined(ARCH_CPU_ARM64)
   [self verifyCrashReportException:EXC_BREAKPOINT];
+#else
+#error Port to your CPU architecture
 #endif
 }
 
@@ -125,15 +139,7 @@
 
 - (void)testBadAccess {
   [rootObject_ crashBadAccess];
-#if defined(NDEBUG)
-#if TARGET_OS_SIMULATOR
-  [self verifyCrashReportException:EXC_BAD_INSTRUCTION];
-#else
-  [self verifyCrashReportException:EXC_BREAKPOINT];
-#endif
-#else
   [self verifyCrashReportException:EXC_BAD_ACCESS];
-#endif
 }
 
 - (void)testException {
diff --git a/third_party/crashpad/crashpad/test/ios/host/cptest_application_delegate.mm b/third_party/crashpad/crashpad/test/ios/host/cptest_application_delegate.mm
index 3bf39df..212ab69 100644
--- a/third_party/crashpad/crashpad/test/ios/host/cptest_application_delegate.mm
+++ b/third_party/crashpad/crashpad/test/ios/host/cptest_application_delegate.mm
@@ -241,7 +241,9 @@
   return [dict passByValue];
 }
 
-- (void)crashBadAccess {
+// Use [[clang::optnone]] here to get consistent exception codes, otherwise the
+// exception can change depending on optimization level.
+- (void)crashBadAccess [[clang::optnone]] {
   strcpy(nullptr, "bla");
 }
 
@@ -249,11 +251,6 @@
   kill(getpid(), SIGABRT);
 }
 
-- (void)crashSegv {
-  long* zero = nullptr;
-  *zero = 0xc045004d;
-}
-
 - (void)crashTrap {
   __builtin_trap();
 }
diff --git a/third_party/crashpad/crashpad/test/ios/host/cptest_shared_object.h b/third_party/crashpad/crashpad/test/ios/host/cptest_shared_object.h
index 90e3de8..2fe36c5 100644
--- a/third_party/crashpad/crashpad/test/ios/host/cptest_shared_object.h
+++ b/third_party/crashpad/crashpad/test/ios/host/cptest_shared_object.h
@@ -58,9 +58,6 @@
 // Triggers a crash with a call to kill(SIGABRT).
 - (void)crashKillAbort;
 
-// Triggers a segfault crash.
-- (void)crashSegv;
-
 // Trigger a crash with a __builtin_trap.
 - (void)crashTrap;
 
diff --git a/third_party/rust/third_party.toml b/third_party/rust/third_party.toml
index 685b7ee..05c80df 100644
--- a/third_party/rust/third_party.toml
+++ b/third_party/rust/third_party.toml
@@ -33,4 +33,4 @@
 cxxbridge-cmd = "1"
 cxx = "1"
 serde = "1"
-serde_jsonrc = "0.1"
+serde_jsonrc = { version="0.1", features=["unbounded_depth"] }
diff --git a/tools/binary_size/libsupersize/viewer/caspian/README.md b/tools/binary_size/libsupersize/viewer/caspian/README.md
index 194fbceb..4696774 100644
--- a/tools/binary_size/libsupersize/viewer/caspian/README.md
+++ b/tools/binary_size/libsupersize/viewer/caspian/README.md
@@ -65,14 +65,24 @@
 
 ## Debugging WASM
 
-Follow this article: https://developer.chrome.com/blog/wasm-debugging-2020/
+Based on this article: https://developer.chrome.com/blog/wasm-debugging-2020/
 
-Make sure to:
+The one-time setup steps are:
 
- * Run Chrome on the same machine you are serving from
- * Use Chrome Dev or Canary (`sudo apt-get install google-chrome-unstable`)
- * Install the reference extension
- * Enabled DWARF debugging in DevTools' experiments panel
+ * On the same machine as your source code:
+   * Install Chrome Canary (`sudo apt-get install google-chrome-unstable`)
+   * Open Chrome Canary
+ * Install the Chrome extension: https://goo.gle/wasm-debugging-extension
+ * Enable DWARF debugging in DevTools' experiments panel
+   * Gears button in top right, then "experiments" on left nav, then filter for "dwarf".
+
+The every-time steps are:
+
+ * Start server: `tools/binary_size/libsupersize/viewer/upload_html_viewer.py --local`
+ * Open `viewer.html` and load a file.
+ * Open DevTools to the "Sources" panel.
+ * Look for `tree-worker-wasm.js  > file://`
+ * Find the `.cc` files within it and set breakpoints.
 
 ## Updating Emscripten Version
 
diff --git a/tools/binary_size/libsupersize/viewer/caspian/wasmbuild.patch b/tools/binary_size/libsupersize/viewer/caspian/wasmbuild.patch
index 9ebc274..456cc17 100644
--- a/tools/binary_size/libsupersize/viewer/caspian/wasmbuild.patch
+++ b/tools/binary_size/libsupersize/viewer/caspian/wasmbuild.patch
@@ -108,6 +108,6 @@
 +  executable_extension = ".js"
 +  link_outputs = [
 +    "{{output_dir}}/{{target_output_name}}.wasm",
-+    "{{output_dir}}/{{target_output_name}}.wasm.map",
++    "{{output_dir}}/{{target_output_name}}.wasm.debug.wasm",
 +  ]
 +}
diff --git a/tools/binary_size/libsupersize/viewer/static/tree-ui.js b/tools/binary_size/libsupersize/viewer/static/tree-ui.js
index 2682ac1c..3e49470 100644
--- a/tools/binary_size/libsupersize/viewer/static/tree-ui.js
+++ b/tools/binary_size/libsupersize/viewer/static/tree-ui.js
@@ -416,7 +416,7 @@
 
   // Process response of an initial load / upload.
   function processLoadTreeResponse(message) {
-    const {diffMode, beforeBlobUrl, loadBlobUrl, isMultiContainer} =
+    const {diffMode, beforeBlobUrl, loadBlobUrl, isMultiContainer, root} =
         message.loadResults;
     console.log(
         '%cPro Tip: %cawait supersize.worker.openNode("$FILE_PATH")',
diff --git a/tools/binary_size/libsupersize/viewer/templates/sw.js b/tools/binary_size/libsupersize/viewer/templates/sw.js
index b048d4d..6053e42e 100644
--- a/tools/binary_size/libsupersize/viewer/templates/sw.js
+++ b/tools/binary_size/libsupersize/viewer/templates/sw.js
@@ -9,7 +9,6 @@
 const filesToCache = [
   'auth.js',
   'auth-consts.js',
-  'caspian_web.wasm.map',
   'caspian_web.js',
   'caspian_web.wasm',
   'favicon.ico',
diff --git a/tools/binary_size/libsupersize/viewer/upload_html_viewer.py b/tools/binary_size/libsupersize/viewer/upload_html_viewer.py
index ad187f9..7ff7129 100755
--- a/tools/binary_size/libsupersize/viewer/upload_html_viewer.py
+++ b/tools/binary_size/libsupersize/viewer/upload_html_viewer.py
@@ -14,14 +14,17 @@
 import urllib.request
 import uuid
 
-_STATIC_FILES_DIR = (pathlib.Path(__file__).parent / 'static').resolve()
+_VIEWER_DIR = pathlib.Path(__file__).parent.resolve()
+_STATIC_FILES_DIR = _VIEWER_DIR / 'static'
 
 _FIREBASE_PROJECT = 'chrome-supersize'
 _PROD_URL = 'https://chrome-supersize.firebaseapp.com/'
 _WASM_FILES = [
     'caspian_web.js',
     'caspian_web.wasm',
-    'caspian_web.wasm.map',
+]
+_DEBUG_WASM_FILES = [
+    'caspian_web.wasm.debug.wasm',
 ]
 
 _PROD = 'prod'
@@ -44,9 +47,8 @@
 
 def _FirebaseInitProjectDir(project_dir):
   """Create a firebase.json file that is needed for deployment."""
-  static_dir = os.path.join(project_dir, 'public')
-  with open(os.path.join(project_dir, 'firebase.json'), 'w') as f:
-    f.write("""
+  static_dir = project_dir / 'public'
+  project_dir.joinpath('firebase.json').write_text("""\
 {
   "hosting": {
     "public": "public",
@@ -91,18 +93,25 @@
     for f in _WASM_FILES:
       print(f'Downloading: {_PROD_URL + f}')
       with urllib.request.urlopen(_PROD_URL + f) as response:
-        with open(_STATIC_FILES_DIR / f, 'wb') as output:
+        with _STATIC_FILES_DIR.joinpath(f).open('wb') as output:
           shutil.copyfileobj(response, output)
 
 
 def _FillInAndCopyTemplates(project_static_dir):
   """Generate and copy over the templates/sw.js file."""
-  template_file = os.path.join(os.path.dirname(__file__), 'templates', 'sw.js')
+  src_path = _VIEWER_DIR / 'templates' / 'sw.js'
+  dst_path = project_static_dir / 'sw.js'
   cache_hash = uuid.uuid4().hex
+  dst_path.write_text(src_path.read_text().replace('{{cache_hash}}',
+                                                   cache_hash))
 
-  with open(template_file, 'r') as in_file:
-    with open(os.path.join(project_static_dir, 'sw.js'), 'w') as out_file:
-      out_file.write(in_file.read().replace('{{cache_hash}}', cache_hash))
+
+def _CopyStaticFiles(project_static_dir, *, include_debug_wasm):
+  shutil.copytree(_STATIC_FILES_DIR, project_static_dir)
+  # Don't upload the debug info since it's machine-dependent and large.
+  if not include_debug_wasm:
+    for f in _DEBUG_WASM_FILES:
+      project_static_dir.joinpath(f).unlink(missing_ok=True)
 
 
 def _Prompt(message):
@@ -144,9 +153,12 @@
     if options.deploy_mode != _DEV:
       _FirebaseLogin()
     with tempfile.TemporaryDirectory(prefix='firebase-') as project_dir:
+      project_dir = pathlib.Path(project_dir)
       _MaybeDownloadWasmFiles(options.download_wasm)
       project_static_dir = _FirebaseInitProjectDir(project_dir)
-      shutil.copytree(_STATIC_FILES_DIR, project_static_dir)
+      _CopyStaticFiles(project_static_dir,
+                       include_debug_wasm=options.deploy_mode == _DEV)
+      _FirebaseLogin()
       _FillInAndCopyTemplates(project_static_dir)
       _FirebaseDeploy(project_dir, deploy_mode=options.deploy_mode)
   else:
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 192002d..dd40d1f5 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -122,7 +122,7 @@
       'android-12-x64-fyi-rel': 'android_release_bot_minimal_symbols_x64_fastbuild_webview_trichrome',
       'android-annotator-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_bot_minimal_symbols_arm64_webview_monochrome',
-      'android-web-platform-pie-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_monochrome',
+      'android-chrome-pie-x86-wpt-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_monochrome',
       'android-weblayer-pie-x86-wpt-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_monochrome',
       'android-weblayer-pie-x86-wpt-smoketest': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_monochrome',
       'android-webview-pie-x86-wpt-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_monochrome_webview_shell',
@@ -275,11 +275,11 @@
     'chromium.fyi': {
       'Afl Upload Linux ASan': 'afl_asan_shared_release_bot',
       'CFI Linux CF (reclient shadow)': 'cfi_full_cfi_icall_cfi_diag_recover_release_static_reclient',
-      'Comparison Linux': {
+      'Comparison Linux (reclient)': {
         'goma': 'gpu_tests_release_bot',
         'reclient': 'gpu_tests_release_bot_reclient',
       },
-      'Comparison Windows': {
+      'Comparison Windows (reclient)': {
         'goma': 'gpu_tests_release_bot_minimal_symbols',
         'reclient': 'gpu_tests_release_bot_minimal_symbols_reclient',
       },
@@ -820,7 +820,7 @@
       'android-pie-arm64-rel-rts': 'android_release_trybot_arm64_webview_monochrome_expectations_fastbuild',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_trybot_arm64_webview_monochrome',
       'android-pie-x86-rel': 'android_release_trybot_x86_fastbuild_webview_monochrome',
-      'android-web-platform-pie-x86-fyi-rel': 'android_release_trybot_x86_fastbuild_webview_monochrome',
+      'android-chrome-pie-x86-wpt-fyi-rel': 'android_release_trybot_x86_fastbuild_webview_monochrome',
       'android-weblayer-10-x86-rel-tests': 'android_release_trybot_minimal_symbols_x86_fastbuild_resource_allowlisting_disable_proguard_chrome_google',
       'android-weblayer-marshmallow-x86-rel-tests': 'android_release_trybot_minimal_symbols_x86_fastbuild_resource_allowlisting_disable_proguard_chrome_google',
       'android-weblayer-pie-x86-rel-tests': 'android_release_trybot_minimal_symbols_x86_fastbuild_disable_proguard_webview_monochrome',
diff --git a/tools/mb/mb_config_expectations/chromium.android.fyi.json b/tools/mb/mb_config_expectations/chromium.android.fyi.json
index eaf75e2..55672ed 100644
--- a/tools/mb/mb_config_expectations/chromium.android.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.android.fyi.json
@@ -77,22 +77,7 @@
       "use_goma": true
     }
   },
-  "android-pie-arm64-wpt-rel-non-cq": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "strip_debug_info": true,
-      "symbol_level": 1,
-      "system_webview_package_name": "com.google.android.apps.chrome",
-      "target_cpu": "arm64",
-      "target_os": "android",
-      "use_goma": true
-    }
-  },
-  "android-web-platform-pie-x86-fyi-rel": {
+  "android-chrome-pie-x86-wpt-fyi-rel": {
     "gn_args": {
       "dcheck_always_on": false,
       "disable_android_lint": true,
@@ -109,6 +94,21 @@
       "use_goma": true
     }
   },
+  "android-pie-arm64-wpt-rel-non-cq": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "strip_debug_info": true,
+      "symbol_level": 1,
+      "system_webview_package_name": "com.google.android.apps.chrome",
+      "target_cpu": "arm64",
+      "target_os": "android",
+      "use_goma": true
+    }
+  },
   "android-weblayer-pie-x86-wpt-fyi-rel": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index a4395ea..5e653402 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -30,7 +30,7 @@
       "use_thin_lto": true
     }
   },
-  "Comparison Linux": {
+  "Comparison Linux (reclient)": {
     "goma": {
       "dcheck_always_on": false,
       "ffmpeg_branding": "Chrome",
@@ -49,7 +49,7 @@
       "use_remoteexec": true
     }
   },
-  "Comparison Windows": {
+  "Comparison Windows (reclient)": {
     "goma": {
       "dcheck_always_on": false,
       "ffmpeg_branding": "Chrome",
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index a5718eb7..a9cc0be 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -95,6 +95,24 @@
       "use_goma": true
     }
   },
+  "android-chrome-pie-x86-wpt-fyi-rel": {
+    "gn_args": {
+      "blink_enable_generated_code_formatting": false,
+      "dcheck_always_on": true,
+      "disable_android_lint": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "strip_debug_info": true,
+      "symbol_level": 0,
+      "system_webview_package_name": "com.google.android.apps.chrome",
+      "target_cpu": "x86",
+      "target_os": "android",
+      "use_errorprone_java_compiler": false,
+      "use_goma": true
+    }
+  },
   "android-cronet-arm-dbg": {
     "gn_args": {
       "arm_use_neon": false,
@@ -779,24 +797,6 @@
       "use_goma": true
     }
   },
-  "android-web-platform-pie-x86-fyi-rel": {
-    "gn_args": {
-      "blink_enable_generated_code_formatting": false,
-      "dcheck_always_on": true,
-      "disable_android_lint": true,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "strip_debug_info": true,
-      "symbol_level": 0,
-      "system_webview_package_name": "com.google.android.apps.chrome",
-      "target_cpu": "x86",
-      "target_os": "android",
-      "use_errorprone_java_compiler": false,
-      "use_goma": true
-    }
-  },
   "android-weblayer-10-x86-rel-tests": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c96b6ea..37d1401 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -12819,6 +12819,16 @@
   <int value="2" label="First service disabled"/>
 </enum>
 
+<enum name="ChromeOSBrowserProcessState">
+  <int value="0" label="Running"/>
+  <int value="1" label="Sleeping"/>
+  <int value="2" label="Uninterruptible Wait"/>
+  <int value="3" label="Zombie"/>
+  <int value="4" label="Traced or Stopped"/>
+  <int value="5" label="Unknown"/>
+  <int value="6" label="Error getting state"/>
+</enum>
+
 <enum name="ChromeOSCameraClientType">
   <int value="0" label="Unknown"/>
   <int value="1" label="Testing"/>
@@ -54808,6 +54818,7 @@
   <int value="667643314" label="LitePageServerPreviews:enabled"/>
   <int value="669097106" label="NtpRealboxMatchOmniboxTheme:disabled"/>
   <int value="669958310" label="EnableSuggestedLocalFiles:disabled"/>
+  <int value="671800756" label="TabStripImprovements:enabled"/>
   <int value="672067370" label="InstallableAmbientBadgeMessage:enabled"/>
   <int value="673588373" label="OmniboxPedalSuggestions:disabled"/>
   <int value="674627327" label="MagnifierNewFocusFollowing:enabled"/>
@@ -55098,6 +55109,7 @@
   <int value="878773995" label="ChromeHomeBottomNavLabels:disabled"/>
   <int value="879538323"
       label="ForceMajorVersionInMinorPositionInUserAgent:enabled"/>
+  <int value="879651295" label="TabStripImprovements:disabled"/>
   <int value="879699575" label="disable-gesture-tap-highlight"/>
   <int value="879992337" label="disable-pull-to-refresh-effect"/>
   <int value="880510010" label="enable-permissions-bubbles"/>
@@ -86607,6 +86619,7 @@
   <int value="0" label="Success"/>
   <int value="1" label="Tab hidden before a frame is presented"/>
   <int value="2" label="Presentation failure"/>
+  <int value="3" label="Tab shown twice without being hidden"/>
 </enum>
 
 <enum name="TabUnderAction">
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 6640c2c..201cf61 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -473,18 +473,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.Canvas.WillReadFrequently"
-    enum="BooleanWillReadFrequently" expires_after="2021-01-31">
-  <obsolete>
-    Expired on 2021-01-31. This metric was replaced with
-    Blink.Canvas.2DLayerBridge.WillReadFrequently
-  </obsolete>
-  <owner>fserb@chromium.org</owner>
-  <summary>
-    Logs if the canvas is initialized to be read frequently or not.
-  </summary>
-</histogram>
-
 <histogram name="Blink.ColorGamut.Destination" enum="Gamut" expires_after="M85">
   <owner>brianosman@chromium.org</owner>
   <owner>mcasas@google.com</owner>
@@ -2875,53 +2863,6 @@
   </token>
 </histogram>
 
-<histogram
-    name="Blink.{Host}.RenderTaskDuration.{Thread}.{Context}.{Resource}.{Filter}"
-    units="microseconds" expires_after="2022-05-11">
-  <obsolete>
-    Removed in M93. This metric was replaced with
-    Blink.{Host}.RenderTaskDuration.{Context}.{Filter}
-  </obsolete>
-  <owner>junov@chromium.org</owner>
-  <owner>fserb@chromium.org</owner>
-  <owner>aaronhk@chromium.org</owner>
-  <summary>
-    Time spent executing a script task that draws content to a {Context} context
-    of a {Host}.
-
-    Note: This metric drops reports on clients with low-resolution clocks, which
-    means these reports will be biased against a portion of the population on
-    Windows. See Windows.HasHighResolutionTimeTicks for the affected sample.
-  </summary>
-  <token key="Host">
-    <variant name="Canvas"/>
-    <variant name="OffscreenCanvas"/>
-  </token>
-  <token key="Thread">
-    <variant name="MainThread"/>
-    <variant name="Worker"/>
-  </token>
-  <token key="Context">
-    <variant name="2D"/>
-    <variant name="ImageBitmap"/>
-    <variant name="WebGL"/>
-    <variant name="WebGL2"/>
-    <variant name="WebGPU"/>
-  </token>
-  <token key="Resource">
-    <variant name="Bitmap"/>
-    <variant name="PassThrough"/>
-    <variant name="SharedBitmap"/>
-    <variant name="SharedImage"/>
-    <variant name="SkiaDawnSharedImage"/>
-    <variant name="SwapChain"/>
-  </token>
-  <token key="Filter">
-    <variant name="All"/>
-    <variant name="Animation"/>
-  </token>
-</histogram>
-
 <histogram name="Blink.{Host}.{Heap}.{Context}" units="KB"
     expires_after="2022-05-11">
   <owner>junov@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index 68bf789..2f78d36 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -143,26 +143,6 @@
   <token key="BluetoothPairedStates" variants="BluetoothPairedStates"/>
 </histogram>
 
-<histogram name="Bluetooth.ChromeOS.Disconnect.Result{BluetoothTransportTypes}"
-    enum="DisconnectResult" expires_after="2022-07-21">
-  <obsolete>
-    Removed 2022-01-25. Removed in favor of
-    Bluetooth.ChromeOS.UserInitiatedDisconnect.Result, because this actually
-    recorded only user initiated disconnect results, and a more general
-    Bluetooth.ChromeOS.DeviceDisconnect was introduced to record all device
-    disconnections. check http://b/215601529 for details.
-  </obsolete>
-  <owner>khorimoto@chromium.org</owner>
-  <owner>cros-connectivity@google.com</owner>
-  <summary>
-    Emitted each time a bluetooth device disconnect attempt completes.
-    {BluetoothTransportTypes}
-  </summary>
-  <token key="BluetoothTransportTypes" variants="BluetoothTransportTypes">
-    <variant name=""/>
-  </token>
-</histogram>
-
 <histogram
     name="Bluetooth.ChromeOS.FastPair.AccountKey.Failure.{FastPairPairingProtocol}"
     enum="FastPairAccountKeyFailure" expires_after="2022-09-20">
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index c730730..f187d4d 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -759,6 +759,24 @@
   <token key="FeatureName" variants="FeaturesLoggingUsageEvents"/>
 </histogram>
 
+<histogram name="ChromeOS.FirmwareUpdateUi.{Source}.DeviceCount"
+    units="devices" expires_after="2023-01-01">
+  <owner>jimmyxgong@chromium.org</owner>
+  <owner>zentaro@chromium.org</owner>
+  <owner>cros-peripherals@google.com</owner>
+  <summary>
+    Records the total number of devices found. OnStartup is recorded when a user
+    first logs in and OnRefresh is recorded for any subsequent request. The
+    cases in which a subsequent request will be made are when the Firmware
+    Update SWA is launched, when an install is completed, and when we detect
+    that a device has been added or removed.
+  </summary>
+  <token key="Source">
+    <variant name="OnRefresh"/>
+    <variant name="OnStartup"/>
+  </token>
+</histogram>
+
 <histogram name="ChromeOS.Gaia.Message.{GaiaAuthFlow}.{MessageName}"
     enum="BooleanReceived" expires_after="2022-06-01">
   <owner>rsorokin@chromium.org</owner>
@@ -1011,6 +1029,22 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Liveness.BrowserStateAtTimeout"
+    enum="ChromeOSBrowserProcessState" expires_after="2023-01-30">
+  <owner>iby@google.com</owner>
+  <owner>xiyuan@google.com</owner>
+  <owner>kaznacheev@google.com</owner>
+  <summary>
+    Records the state of the Chrome browser process at the moment
+    session_manager's LivenessChecker ping times out. This is the moment when
+    LivenessChecker will send an abort signal, if sending an abort signal is
+    enabled, but this metric is recorded regardless of whether or not aborting
+    from LivenessChecker is enabled. Exactly one sample of this metric is
+    recorded when the timeout occurs. If aborting is enabled, this should mean
+    one sample per abort.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.Liveness.PingResponseTime" units="ms"
     expires_after="2022-08-05">
   <owner>rtinkoff@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index b44b064..2030bcce 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -654,19 +654,6 @@
   </summary>
 </histogram>
 
-<histogram name="ContentSuggestions.Feed.LoadTime" units="ms"
-    expires_after="M89">
-  <obsolete>
-    Removed along with FeedV1 in M89
-  </obsolete>
-  <owner>carlosk@chromium.org</owner>
-  <owner>harringtond@chromium.org</owner>
-  <owner>feed@chromium.org</owner>
-  <summary>
-    The time it takes for the Feed to load entries from the storage.
-  </summary>
-</histogram>
-
 <histogram
     name="ContentSuggestions.Feed.Network.CompressedResponseSizeKB{NetworkRequestType}"
     units="KB" expires_after="2023-03-01">
@@ -747,19 +734,6 @@
   <token key="Type" variants="FeedNetworkRequestType"/>
 </histogram>
 
-<histogram name="ContentSuggestions.Feed.Network.TokenDuration" units="ms"
-    expires_after="M89">
-  <obsolete>
-    Removed along with FeedV1 in M89
-  </obsolete>
-  <owner>carlosk@chromium.org</owner>
-  <owner>harringtond@chromium.org</owner>
-  <owner>feed@chromium.org</owner>
-  <summary>
-    The amount of time it takes to get an access token for signed in users.
-  </summary>
-</histogram>
-
 <histogram name="ContentSuggestions.Feed.Network.TokenFetchStatus"
     enum="GoogleServiceAuthError" expires_after="2023-03-01">
   <owner>carlosk@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index ceb4b88..bc6bb89 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -1871,21 +1871,6 @@
   <summary>Result of a single attempt to upload system logs.</summary>
 </histogram>
 
-<histogram name="Enterprise.UploadJobSuccess" enum="EnterpriseUploadJobSuccess"
-    expires_after="M87">
-  <obsolete>
-    Removed 2022/01
-  </obsolete>
-  <owner>bmalcolm@chromium.org</owner>
-  <owner>managed-devices@google.com</owner>
-  <summary>
-    Number of retries the client did to execute an UploadJob. It's recorded
-    after the request has been completed, either successfully after 0 or more
-    retries, with a failure because of too many retries or if the UploadJob is
-    interrupted (destroyed before it could succeed or fail).
-  </summary>
-</histogram>
-
 <histogram name="Enterprise.UserPolicyChromeOS.ChildUser.OAuthTokenError"
     enum="GoogleServiceAuthError" expires_after="2022-06-26">
   <owner>agawronska@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index d846780..e1569ad 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -41,13 +41,6 @@
 <variants name="KeySystemWithRobustness">
   <variant name="ClearKey" summary="Clear Key key system"/>
   <variant name="Unknown" summary="Unknown key system"/>
-  <variant name="Widevine" summary="Widevine key system">
-    <obsolete>
-      Deprecated as of 11/2021, when we split some Media.EME.Widevine UMAs into
-      Media.EME.Widevine.HardwareSecure and Media.EME.Widevine.SoftwareSecure
-      ones.
-    </obsolete>
-  </variant>
   <variant name="Widevine.HardwareSecure"
       summary="Hardware secure Widevine key system"/>
   <variant name="Widevine.SoftwareSecure"
@@ -940,15 +933,6 @@
   </summary>
 </histogram>
 
-<histogram name="Media.Audio.Render.OutputStreamDuration" units="ms"
-    expires_after="2022-01-30">
-  <obsolete>
-    Removed Mar 2020. Use Media.Audio.Render.OutputStreamDuration2 instead.
-  </obsolete>
-  <owner>olka@chromium.org</owner>
-  <owner>henrika@chromium.org</owner>
-</histogram>
-
 <histogram name="Media.Audio.Render.OutputStreamDuration2" units="ms"
     expires_after="2023-01-21">
   <owner>olka@chromium.org</owner>
@@ -3061,33 +3045,6 @@
   </summary>
 </histogram>
 
-<histogram name="Media.MojoVideoDecoderServiceTiming.{Impl}.{Method}"
-    units="ms" expires_after="M95">
-  <obsolete>
-    Removed Aug 2021.
-  </obsolete>
-  <owner>dalecurtis@chromium.org</owner>
-  <owner>media-dev@chromium.org</owner>
-  <summary>
-    The duration of MojoVideoDecoderService method calls. Recorded every call.
-  </summary>
-  <token key="Impl">
-    <variant name="Alternate"
-        summary="Timing for the kAlternate media::VideoDecoderImplementation.">
-      <obsolete>
-        Removed when D3D11/DXVA VideoDecoder implementations became mutually
-        exclusive in M93.
-      </obsolete>
-    </variant>
-    <variant name="Default"
-        summary="Timing for the kDefault media::VideoDecoderImplementation."/>
-  </token>
-  <token key="Method">
-    <variant name="Construct" summary="The time taken for Construct()."/>
-    <variant name="Destruct" summary="Time time taken for destruction."/>
-  </token>
-</histogram>
-
 <histogram name="Media.MSE.AudioCodec" enum="MSECodec" expires_after="never">
 <!-- expires-never: Codec support planning metric. -->
 
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 3d704ace..720030f 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -562,6 +562,18 @@
 </histogram>
 
 <histogram
+    name="OptimizationGuide.PageContentAnnotations.AnnotateVisitResultCached"
+    enum="BooleanCacheHit" expires_after="M106">
+  <owner>mcrouse@chromium.org</owner>
+  <owner>chrome-intelligence-core@google.com</owner>
+  <summary>
+    Records whether the requested annotation for a visit was provided by the
+    in-memory cache or required them to be determined by executing the models.
+    Recorded once for every requested visit annotation.
+  </summary>
+</histogram>
+
+<histogram
     name="OptimizationGuide.PageContentAnnotations.BatchRequestedSize.{AnnotationType}"
     units="counts" expires_after="M106">
   <owner>robertogden@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index cf24c94..c0ed65d2 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -2670,6 +2670,54 @@
   </metric>
 </event>
 
+<event name="Blink.FedCm">
+  <owner>yigu@chromium.org</owner>
+  <owner>webid-core@google.com</owner>
+  <summary>
+    Records performance metrics for FedCM(Federated Credential Management) API.
+  </summary>
+  <metric name="Status.RequestIdToken" enum="FedCmRequestIdTokenStatus">
+    <summary>
+      Records the status of a request id token call to the FedCM API.
+    </summary>
+  </metric>
+  <metric name="Status.Revoke" enum="FedCmRevokeStatus">
+    <summary>
+      Records the status of a revoke call to the FedCM API.
+    </summary>
+  </metric>
+  <metric name="Timing.CancelOnDialog">
+    <summary>
+      Records the time (in milliseconds) from when the accounts dialog is shown
+      to when the user closes the dialog without selecting any account.
+    </summary>
+  </metric>
+  <metric name="Timing.ContinueOnDialog">
+    <summary>
+      Records the time (in milliseconds) from when the accounts dialog is shown
+      to when the user presses the Continue button.
+    </summary>
+  </metric>
+  <metric name="Timing.IdTokenResponse">
+    <summary>
+      Records the time (in milliseconds) from when the user presses the Continue
+      button to when the id token response is received.
+    </summary>
+  </metric>
+  <metric name="Timing.ShowAccountsDialog">
+    <summary>
+      Records the time (in milliseconds) from when a call to the API was made to
+      when the accounts dialog is shown.
+    </summary>
+  </metric>
+  <metric name="Timing.TurnaroundTime">
+    <summary>
+      Records the overall time (in milliseconds) from when the API is called to
+      when the id token response is received.
+    </summary>
+  </metric>
+</event>
+
 <event name="Blink.FindInPage" singular="True">
   <owner>vmpstr@chromium.org</owner>
   <owner>chrishtr@chromium.org</owner>
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 829cd7d9..30cd48d 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -650,7 +650,7 @@
 ANDROID_PIXEL4A_POWER = PerfPlatform('android-pixel4a_power-perf',
                                      'Android QD4A.200102.001.A1',
                                      _ANDROID_PIXEL4A_POWER_BENCHMARK_CONFIGS,
-                                     10, 'android')
+                                     12, 'android')
 
 # Cros/Lacros
 LACROS_EVE_PERF = PerfPlatform('lacros-eve-perf', '',
diff --git a/tools/perf/core/perf_benchmark.py b/tools/perf/core/perf_benchmark.py
index d6298aa8..1759f276 100644
--- a/tools/perf/core/perf_benchmark.py
+++ b/tools/perf/core/perf_benchmark.py
@@ -130,6 +130,8 @@
       return 'linux'
     if target_os == 'cros':
       return 'chromeos'
+    if target_os == 'lacros':
+      return 'chromeos_lacros'
     return target_os
 
   def _GetVariationsBrowserArgs(self,
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 12a9dc2..4b7bca4 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "9a67df2e38d08a8179971d986d915479b5e0fb47",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/383df8c3092e183e0335ba3f1c502cc660c523dc/trace_processor_shell.exe"
+            "hash": "38e96510b19d13b09e7a75ff74663f45c795598a",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/33928bc89438655f0ab59f4fb8c56ae4a6aa2afa/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "7270d5dd8c35977685779103677c14d9b2839ddc",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/383df8c3092e183e0335ba3f1c502cc660c523dc/trace_processor_shell"
+            "hash": "45900341c5c8a8e38b7b56a3150f441ad9c3a65a",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/448ab50107d2f6382e413564e51e8130e92c4e09/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c0397e87456ad6c6a7aa0133e5b81c97adbab4ab",
             "remote_path": "perfetto_binaries/trace_processor_shell/mac_arm64/cefb3e0ec3a0580c996f801e854fe02963c03d5c/trace_processor_shell"
         },
         "linux": {
-            "hash": "ee50898c0a4577b4a7ab121c958522c13dc0ab0c",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/383df8c3092e183e0335ba3f1c502cc660c523dc/trace_processor_shell"
+            "hash": "b7042c0b9d667bdb81d120c7c1f5b847ec05229e",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/33928bc89438655f0ab59f4fb8c56ae4a6aa2afa/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/android-pixel4a_power-perf_map.json b/tools/perf/core/shard_maps/android-pixel4a_power-perf_map.json
index 1a158320..fe3b3ed 100644
--- a/tools/perf/core/shard_maps/android-pixel4a_power-perf_map.json
+++ b/tools/perf/core/shard_maps/android-pixel4a_power-perf_map.json
@@ -3,9 +3,6 @@
         "benchmarks": {
             "power.mobile": {
                 "abridged": false
-            },
-            "system_health.scroll_jank_mobile": {
-                "abridged": false
             }
         }
     },
@@ -72,6 +69,22 @@
             }
         }
     },
+    "10": {
+        "benchmarks": {
+            "system_health.scroll_jank_mobile": {
+                "end": 5,
+                "abridged": false
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
+            "system_health.scroll_jank_mobile": {
+                "begin": 5,
+                "abridged": false
+            }
+        }
+    },
     "extra_infos": {
         "num_stories": 5,
         "predicted_min_shard_time": 318.0,
diff --git a/ui/accessibility/extensions/colorenhancer/src/background.js b/ui/accessibility/extensions/colorenhancer/src/background.js
index 01b8fbc..5da2674 100644
--- a/ui/accessibility/extensions/colorenhancer/src/background.js
+++ b/ui/accessibility/extensions/colorenhancer/src/background.js
@@ -4,92 +4,104 @@
 
 importScripts('./common.js', './storage.js');
 
-const storage = new Storage();
+class Background {
+  constructor() {
+    /** @private {Storage} */
+    this.storage_ = new Storage();
 
-function forEachTab(tabCallback) {
-  chrome.windows.getAll({'populate': true}, windows => {
-    for (const w of windows) {
-      for (const tab of w.tabs) {
-        if (isDisallowedUrl(tab.url)) {
-          continue;
+    this.init_();
+  }
+
+  /**
+   * @param {function(chrome.tabs.Tab)} tabCallback A function that performs
+   *     an action on each tab
+   * @private
+   */
+  forEachTab_(tabCallback) {
+    chrome.windows.getAll({'populate': true}, windows => {
+      for (const w of windows) {
+        for (const tab of w.tabs) {
+          if (isDisallowedUrl(tab.url)) {
+            continue;
+          }
+          tabCallback(tab);
         }
-        tabCallback(tab);
       }
+    });
+  }
+
+  /**
+   * Adds filter script and css to all existing tabs.
+   *
+   * TODO(wnwen): Verify content scripts are not being injected multiple times.
+   * @private
+   */
+  injectContentScripts_() {
+    this.forEachTab_(tab => chrome.scripting.executeScript({
+        target: {tabId: tab.id},
+        files: ['src/common.js', 'src/cvd.js'],
+      }));
+  }
+
+  /**
+   * Updates all existing tabs with config values.
+   * @private
+   */
+  updateTabs_() {
+    this.forEachTab_(async (tab) => {
+      const msg = {
+        'delta': await this.storage_.getSiteDelta(siteFromUrl(tab.url)),
+        'severity': await this.storage_.getDefaultSeverity(),
+        'type': await this.storage_.getDefaultType(),
+        'simulate': await this.storage_.getDefaultSimulate(),
+        'enable': await this.storage_.getDefaultEnable()
+      };
+      debugPrint('updateTabs: sending ' + JSON.stringify(msg) + ' to ' +
+          siteFromUrl(tab.url));
+      chrome.tabs.sendMessage(tab.id, msg);
+    });
+  }
+
+  /** @private */
+  async onInitReceived_(sender) {
+    let delta;
+    if (sender.tab) {
+      delta = await this.storage_.getSiteDelta(siteFromUrl(sender.tab.url));
+    } else {
+      delta = await this.storage_.getDefaultDelta();
     }
-  });
-}
 
-/**
- * Adds filter script and css to all existing tabs.
- *
- * TODO(wnwen): Verify content scripts are not being injected multiple times.
- */
-function injectContentScripts() {
-  forEachTab(tab => chrome.scripting.executeScript({
-      target: {tabId: tab.id},
-      files: ['src/common.js', 'src/cvd.js'],
-    }));
-}
-
-/**
- * Updates all existing tabs with config values.
- */
-function updateTabs() {
-  forEachTab(async function(tab) {
-    const msg = {
-      'delta': await storage.getSiteDelta(siteFromUrl(tab.url)),
-      'severity': await storage.getDefaultSeverity(),
-      'type': await storage.getDefaultType(),
-      'simulate': await storage.getDefaultSimulate(),
-      'enable': await storage.getDefaultEnable()
+    return {
+      'delta': delta,
+      'severity': await this.storage_.getDefaultSeverity(),
+      'type': await this.storage_.getDefaultType(),
+      'simulate': await this.storage_.getDefaultSimulate(),
+      'enable': await this.storage_.getDefaultEnable()
     };
-    debugPrint('updateTabs: sending ' + JSON.stringify(msg) + ' to ' +
-        siteFromUrl(tab.url));
-    chrome.tabs.sendMessage(tab.id, msg);
-  });
-}
-
-async function onInitReceived(sender) {
-  let delta;
-  if (sender.tab) {
-    delta = await storage.getSiteDelta(siteFromUrl(sender.tab.url));
-  } else {
-    delta = await storage.getDefaultDelta();
   }
 
-  return {
-    'delta': delta,
-    'severity': await storage.getDefaultSeverity(),
-    'type': await storage.getDefaultType(),
-    'simulate': await storage.getDefaultSimulate(),
-    'enable': await storage.getDefaultEnable()
-  };
+  /**
+   * Initial extension loading.
+   * @private
+   */
+  init_() {
+    this.injectContentScripts_();
+    this.updateTabs_();
+
+    chrome.runtime.onMessage.addListener(
+        (message, sender, sendResponse) => {
+          if (message === 'init') {
+            this.onInitReceived_(sender).then(sendResponse);
+            return true;  // Keep message context open for async response.
+          } else if (message === 'updateTabs') {
+            this.updateTabs_();
+          }
+        });
+    chrome.storage.onChanged.addListener(this.updateTabs_.bind(this));
+
+    //TODO(mustaq): Handle uninstall
+
+  }
 }
 
-/**
- * Initial extension loading.
- */
-(function initialize() {
-  injectContentScripts();
-  updateTabs();
-
-  chrome.runtime.onMessage.addListener(
-      function(message, sender, sendResponse) {
-        if (message === 'init') {
-          onInitReceived(sender).then(sendResponse);
-          return true;  // Keep message context open for async response.
-        }
-      });
-
-  //TODO(mustaq): Handle uninstall
-
-  chrome.storage.onChanged.addListener(function() {
-    updateTabs();
-  });
-})();
-
-chrome.runtime.onMessage.addListener(message => {
-  if (message === 'updateTabs') {
-    updateTabs();
-  }
-});
+const background = new Background();
diff --git a/ui/accessibility/extensions/colorenhancer/src/cvd.js b/ui/accessibility/extensions/colorenhancer/src/cvd.js
index cc9de65..bed924b 100644
--- a/ui/accessibility/extensions/colorenhancer/src/cvd.js
+++ b/ui/accessibility/extensions/colorenhancer/src/cvd.js
@@ -56,80 +56,89 @@
     [0, 0, 1]
   ];
 
+  /** @enum {number} */
+  const Index = {
+    ZERO: 0,
+    ONE: 1,
+    TWO: 2,
+  };
+
+  /** @typedef {!Array<!Array<number>>} */
+  let Matrix;
 
   /**
-   * Adds two matrices.
-   * @param {!object} m1 A 3x3 matrix.
-   * @param {!object} m2 A 3x3 matrix.
-   * @return {!object} The 3x3 matrix m1 + m2.
+   * Creates a matrix with elements specified by the elementCalculator function.
+   * @param {!function(!Index, !Index): number} elementCalculator Given the i
+   *     and j indices of the element, calculates the value for the new matrix.
+   * @return {!Matrix}
    */
-  function add3x3(m1, m2) {
+  function matrixMaker3x3(elementCalculator) {
     const result = [];
-    for (let i = 0; i < 3; i++) {
+    for (const i of Object.values(Index)) {
       result[i] = [];
-      for (let j = 0; j < 3; j++) {
-        result[i].push(m1[i][j] + m2[i][j]);
+      for (const j of Object.values(Index)) {
+        result[i][j] = elementCalculator(i, j);
       }
     }
     return result;
   }
 
+  /**
+   * Adds two matrices.
+   * @param {!Matrix} m1 A 3x3 matrix.
+   * @param {!Matrix} m2 A 3x3 matrix.
+   * @return {!Matrix} The 3x3 matrix m1 + m2.
+   */
+  function add3x3(m1, m2) {
+    /** @type {!function(!Index, !Index): number} */
+    const adder = (i, j) => m1[i][j] + m2[i][j];
+    return matrixMaker3x3(adder);
+  }
+
 
   /**
    * Subtracts one matrix from another.
-   * @param {!object} m1 A 3x3 matrix.
-   * @param {!object} m2 A 3x3 matrix.
-   * @return {!object} The 3x3 matrix m1 - m2.
+   * @param {!Matrix} m1 A 3x3 matrix.
+   * @param {!Matrix} m2 A 3x3 matrix.
+   * @return {!Matrix} The 3x3 matrix m1 - m2.
    */
   function sub3x3(m1, m2) {
-    const result = [];
-    for (let i = 0; i < 3; i++) {
-      result[i] = [];
-      for (let j = 0; j < 3; j++) {
-        result[i].push(m1[i][j] - m2[i][j]);
-      }
-    }
-    return result;
+    /** @type {!function(!Index, !Index): number} */
+    const subtracter = (i, j) => m1[i][j] - m2[i][j];
+    return matrixMaker3x3(subtracter);
   }
 
 
   /**
    * Multiplies one matrix with another.
-   * @param {!object} m1 A 3x3 matrix.
-   * @param {!object} m2 A 3x3 matrix.
-   * @return {!object} The 3x3 matrix m1 * m2.
+   * @param {!Matrix} m1 A 3x3 matrix.
+   * @param {!Matrix} m2 A 3x3 matrix.
+   * @return {!Matrix} The 3x3 matrix m1 * m2.
    */
   function mul3x3(m1, m2) {
-    const result = [];
-    for (let i = 0; i < 3; i++) {
-      result[i] = [];
-      for (let j = 0; j < 3; j++) {
-        let sum = 0;
-        for (let k = 0; k < 3; k++) {
-          sum += m1[i][k] * m2[k][j];
-        }
-        result[i].push(sum);
+    /** @type {!function(!Index, !Index): number} */
+    const multiplier = (i, j) => {
+      let sum = 0;
+      for (const k of Object.values(Index)) {
+        sum += m1[i][k] * m2[k][j];
       }
+      return sum;
     }
-    return result;
+
+    return matrixMaker3x3(multiplier);
   }
 
 
   /**
    * Multiplies a matrix with a number.
-   * @param {!object} m A 3x3 matrix.
+   * @param {!Matrix} m A 3x3 matrix.
    * @param {!number} k A scalar multiplier.
-   * @return {!object} The 3x3 matrix m * k.
+   * @return {!Matrix} The 3x3 matrix m * k.
    */
   function mul3x3Scalar(m, k) {
-    const result = [];
-    for (let i = 0; i < 3; i++) {
-      result[i] = [];
-      for (let j = 0; j < 3; j++) {
-        result[i].push(k * m[i][j]);
-      }
-    }
-    return result;
+    /** @type {!function(!Index, !Index): number} */
+    const scaler = (i, j) => k * m[i][j];
+    return matrixMaker3x3(scaler);
   }
 
 
@@ -137,7 +146,7 @@
 
   /**
    * Makes the SVG matrix string (of 20 values) for a given matrix.
-   * @param {!object} m A 3x3 matrix.
+   * @param {!Matrix} m A 3x3 matrix.
    * @return {!string} The SVG matrix string for m.
    */
   function svgMatrixStringFrom3x3(m) {
@@ -153,7 +162,7 @@
 
   /**
    * Makes a human readable string for a given matrix.
-   * @param {!object} m A 3x3 matrix.
+   * @param {!Matrix} m A 3x3 matrix.
    * @return {!string} A human-readable string for m.
    */
   function humanReadbleStringFrom3x3(m) {
@@ -265,23 +274,19 @@
    * @param {string} cvdType Type of CVD, either "PROTANOMALY" or
    *     "DEUTERANOMALY" or "TRITANOMALY".
    * @param {number} severity A real number in [0,1] denoting severity.
+   * @return {!Matrix}
    */
   function getCvdSimulationMatrix(cvdType, severity) {
     const cvdSimulationParam = cvdSimulationParams[cvdType];
     const severity_squared = severity * severity;
-    const matrix = [];
-    for (let i = 0; i < 3; i++) {
-      const row = [];
-      for (let j = 0; j < 3; j++) {
-        const paramRow = i*3+j;
-        const val = cvdSimulationParam[paramRow][0] * severity_squared
-                  + cvdSimulationParam[paramRow][1] * severity
-                  + cvdSimulationParam[paramRow][2];
-        row.push(val);
-      }
-      matrix.push(row);
+
+    const calculateElementValue = (i, j) => {
+      const paramRow = i*3+j;
+      return cvdSimulationParam[paramRow][0] * severity_squared
+           + cvdSimulationParam[paramRow][1] * severity
+           + cvdSimulationParam[paramRow][2];
     }
-    return matrix;
+    return matrixMaker3x3(calculateElementValue);
   }
 
 
@@ -291,6 +296,7 @@
    * @param {string} cvdType Type of CVD, either "PROTANOMALY" or
    *     "DEUTERANOMALY" or "TRITANOMALY".
    * @param {number} delta A real number in [0,1] denoting color adjustment.
+   * @return {!Matrix}
    */
   function getCvdCorrectionMatrix(cvdType, delta) {
     cvdCorrectionParam = cvdCorrectionParams[cvdType];
@@ -308,6 +314,7 @@
    * @param {number} delta A real number in [0,1] denoting color adjustment.
    * @param {boolean} simulate Whether to simulate the CVD type.
    * @param {boolean} enable Whether to enable color filtering.
+   * @return {!Matrix}
    */
   function getEffectiveCvdMatrix(cvdType, severity, delta, simulate, enable) {
     if (!enable) {
@@ -340,7 +347,6 @@
   function addElements() {
     let style = document.getElementById(STYLE_ID);
     if (!style) {
-      const baseUrl = window.location.href.replace(window.location.hash, '');
       style = document.createElement('style');
       style.id = STYLE_ID;
       style.setAttribute('type', 'text/css');
@@ -490,7 +496,7 @@
 
   /**
    * Adds support for a color enhancement filter.
-   * @param {!Object} matrix 3x3 RGB transformation matrix.
+   * @param {!Matrix} matrix 3x3 RGB transformation matrix.
    */
   exports.injectColorEnhancementFilter = function(matrix) {
     setFilter(matrix);
diff --git a/ui/accessibility/extensions/colorenhancer/src/storage.js b/ui/accessibility/extensions/colorenhancer/src/storage.js
index e9ef887..a4299a85 100644
--- a/ui/accessibility/extensions/colorenhancer/src/storage.js
+++ b/ui/accessibility/extensions/colorenhancer/src/storage.js
@@ -3,8 +3,116 @@
 // found in the LICENSE file.
 
 // TODO(wnwen): Wrap calls.
+// TODO(anastasi): Change interface to remove unneeded promises.
+
+/** @typedef {string|number|boolean} */
+let StoredValue;
+/** @typedef {{newValue: StoredValue, oldValue: StoredValue}} */
+let Change;
 
 class Storage {
+  constructor() {
+    /** @private {number} */
+    this.defaultDelta_ = Storage.DEFAULT_DELTA;
+
+    /** @private {!Object<string, number>} */
+    this.siteDeltas_ = {};
+
+    /** @private {number} */
+    this.defaultSeverity_ = Storage.DEFAULT_SEVERITY;
+
+    /** @private {string} */
+    this.defaultType_ = Storage.INVALID_TYPE_PLACEHOLDER;
+
+    /** @private {boolean} */
+    this.defaultSimulate_ = Storage.DEFAULT_SIMULATE;
+
+    /** @private {boolean} */
+    this.defaultEnable_ = Storage.DEFAULT_ENABLE;
+
+    this.init_();
+  }
+
+  /** @private */
+  init_() {
+    chrome.storage.onChanged.addListener(
+        this.onStorageChanged_.bind(this, (change) => change.newValue));
+    chrome.storage.local.get(
+        null /** all items */,
+        this.onStorageChanged_.bind(this, (newVal) => newVal));
+  }
+
+  /**
+   * Updates the cached values.
+   * @param {(function(Change): StoredValue) |
+   *    (function(StoredValue): StoredValue)} getValueFromAPIResult Gets the
+   *      new value from the result indexed at a key. When called by
+   *      storage.local.get(), this returns exactly what it is given. When
+   *      called by storage.onChanged, this extracts newValue (instead of
+   *      oldValue).
+   * @param {Object<string, Change>|Object<string, StoredValue>} changes The
+   *      updates from the chrome.storage API.
+   * @private
+   */
+  onStorageChanged_(getValueFromAPIResult, changes) {
+    if (changes[Storage.DELTA_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.DELTA_TAG]);
+      if (this.validDelta_(newVal)) {
+        this.defaultDelta_ = newVal;
+      } else {
+        this.defaultDelta_ = Storage.DEFAULT_DELTA;
+      }
+    }
+
+    if (changes[Storage.PER_SITE_DELTA_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.PER_SITE_DELTA_TAG]);
+      if (typeof (newVal) === 'object') {
+        for (const site of Object.keys(newVal)) {
+          if (!this.validDelta_(newVal[site])) {
+            newVal[site] = this.defaultDelta_;
+          }
+        }
+        this.siteDeltas_ = newVal;
+      } else {
+        this.siteDeltas_ = {};
+      }
+    }
+
+    if (changes[Storage.SEVERITY_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.SEVERITY_TAG]);
+      if (this.validSeverity_(newVal)) {
+        this.defaultSeverity_ = newVal;
+      } else {
+        this.defaultSeverity_ = Storage.DEFAULT_SEVERITY;
+      }
+    }
+
+    if (changes[Storage.TYPE_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.TYPE_TAG]);
+      if (this.validType_(newVal)) {
+        this.defaultType_ = newVal;
+      }
+    }
+
+    if (changes[Storage.SIMULATE_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.SIMULATE_TAG]);
+      if (this.validBoolean_(newVal)) {
+        this.defaultSimulate_ = newVal;
+      } else {
+        this.defaultSimulate_ = Storage.DEFAULT_SIMULATE;
+      }
+    }
+
+    if (changes[Storage.ENABLE_TAG]) {
+      const newVal = getValueFromAPIResult(changes[Storage.ENABLE_TAG]);
+      if (this.validBoolean_(newVal)) {
+        this.defaultEnable_ = newVal;
+      } else {
+        this.defaultEnable_ = Storage.DEFAULT_ENABLE;
+      }
+    }
+  }
+
   // ======= Delta setting =======
 
   /**
@@ -19,15 +127,7 @@
   /** @return {Promise<number>} */
   getDefaultDelta() {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.DELTA_TAG], (result) => {
-        let delta = result[Storage.DELTA_TAG];
-        if (this.validDelta_(delta)) {
-          resolve(delta);
-          return;
-        }
-        delta = Storage.DEFAULT_DELTA;
-        this.store_(Storage.DELTA_TAG, delta, () => resolve(delta));
-      });
+      resolve(this.defaultDelta_);
     });
   }
 
@@ -39,6 +139,7 @@
     if (!this.validDelta_(delta)) {
       delta = Storage.DEFAULT_DELTA;
     }
+    this.defaultDelta_ = delta;
     return new Promise(
         resolve => this.store_(Storage.DELTA_TAG, delta, resolve));
   }
@@ -49,15 +150,13 @@
    */
   getSiteDelta(site) {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.PER_SITE_DELTA_TAG], (result) => {
-        const siteDeltas = result [Storage.PER_SITE_DELTA_TAG] || {};
-        const delta = siteDeltas[site];
-        if (!this.validDelta_(delta)) {
-          this.getDefaultDelta().then(resolve);
-          return;
-        }
-        resolve(delta);
-      });
+      const delta = this.siteDeltas_[site];
+      if (!this.validDelta_(delta)) {
+        this.setSiteDelta(site, this.defaultDelta_);
+        resolve(this.defaultDelta_);
+        return;
+      }
+      resolve(delta);
     });
   }
 
@@ -67,20 +166,18 @@
    * @return {Promise}
    */
   setSiteDelta(site, delta) {
-    return new Promise(async resolve => {
+    return new Promise(resolve => {
       if (!this.validDelta_(delta)) {
-        delta = await this.getDefaultDelta();
+        delta = this.defaultDelta_;
       }
-      chrome.storage.local.get([Storage.PER_SITE_DELTA_TAG], (result) => {
-        const siteDeltas = result[Storage.PER_SITE_DELTA_TAG] || {};
-        siteDeltas[site] = delta;
-        this.store_(Storage.PER_SITE_DELTA_TAG, siteDeltas, resolve);
-      });
+      this.siteDeltas_[site] = delta;
+      this.store_(Storage.PER_SITE_DELTA_TAG, this.siteDeltas_, resolve);
     });
   }
 
   /** @return {Promise} */
   resetSiteDeltas() {
+    this.siteDeltas_ = {};
     return new Promise(
         resolve => this.store_(Storage.PER_SITE_DELTA_TAG, {}, resolve));
   }
@@ -99,15 +196,7 @@
   /** @return {Promise<number>} */
   getDefaultSeverity() {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.SEVERITY_TAG], (result) => {
-        let severity = result[Storage.SEVERITY_TAG];
-        if (this.validSeverity_(severity)) {
-          resolve(severity);
-          return;
-        }
-        severity = Storage.DEFAULT_SEVERITY;
-        this.store_(Storage.SEVERITY_TAG, severity, () => resolve(severity));
-      });
+      resolve(this.defaultSeverity_);
     });
   }
 
@@ -119,6 +208,7 @@
     if (!this.validSeverity_(severity)) {
       severity = Storage.DEFAULT_SEVERITY;
     }
+    this.defaultSeverity_ = severity;
     return new Promise(
         resolve => this.store_(Storage.SEVERITY_TAG, severity, resolve));
   }
@@ -138,15 +228,7 @@
   /** @return {Promise<string>} */
   getDefaultType() {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.TYPE_TAG], (result) => {
-        const type = result[Storage.TYPE_TAG];
-        if (this.validType_(type)) {
-          resolve(type);
-        } else {
-          // TODO(anastasi): add appropriate error handling
-          resolve(Storage.INVALID_TYPE_PLACEHOLDER);
-        }
-      });
+      resolve(this.defaultType_);
     });
   }
 
@@ -158,6 +240,7 @@
     if (!this.validType_(type)) {
       type = Storage.INVALID_TYPE_PLACEHOLDER;
     }
+    this.defaultType_ = type;
     return new Promise(resolve => this.store_(Storage.TYPE_TAG, type, resolve));
   }
 
@@ -166,16 +249,7 @@
   /** @return {Promise<boolean>} */
   getDefaultSimulate() {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.SIMULATE_TAG], (result) => {
-        let simulate = result[Storage.SIMULATE_TAG];
-
-        if (this.validBoolean_(simulate)) {
-          resolve(simulate);
-          return;
-        }
-        simulate = Storage.DEFAULT_SIMULATE;
-        this.store_(Storage.SIMULATE_TAG, simulate, () => resolve(simulate));
-      });
+      resolve(this.defaultSimulate_);
     });
   }
 
@@ -187,6 +261,7 @@
     if (!this.validBoolean_(simulate)) {
       simulate = Storage.DEFAULT_SIMULATE;
     }
+    this.defaultSimulate_ = simulate;
     return new Promise(
         resolve => this.store_(Storage.SIMULATE_TAG, simulate, resolve));
   }
@@ -196,16 +271,7 @@
   /** @return {Promise<boolean>} */
   getDefaultEnable() {
     return new Promise(resolve => {
-      chrome.storage.local.get([Storage.ENABLE_TAG], (result) => {
-        let enable = result[Storage.ENABLE_TAG];
-
-        if (this.validBoolean_(enable)) {
-          resolve(enable);
-          return;
-        }
-        enable = Storage.DEFAULT_ENABLE;
-        this.store_(Storage.ENABLE_TAG, enable, () => resolve(enable));
-      });
+      resolve(this.defaultEnable_);
     });
   }
 
@@ -217,6 +283,7 @@
     if (!this.validBoolean_(enable)) {
       enable = Storage.DEFAULT_ENABLE;
     }
+    this.defaultEnable_ = enable;
     return new Promise(
         resolve => this.store_(Storage.ENABLE_TAG, enable, resolve));
   }
diff --git a/ui/android/java/res/values/attrs.xml b/ui/android/java/res/values/attrs.xml
index 7e8b195f..0e85ac7 100644
--- a/ui/android/java/res/values/attrs.xml
+++ b/ui/android/java/res/values/attrs.xml
@@ -8,21 +8,6 @@
     <!-- The top or bottom inset of a drawable. -->
     <attr name="verticalInset" format="reference|dimension"/>
 
-    <!-- Used in SwitchMaterial tint lists because the color roles are different in light and dark
-         themes. -->
-    <attr name="colorSwitchTrackNormal" format="color"/>
-    <attr name="colorSwitchThumbDisabled" format="color"/>
-    <!-- Splintered SwitchMaterial roles to roll back dynamic colors. -->
-    <attr name="colorPrimaryNonDynamic" format="color"/>
-    <attr name="colorPrimaryContainerNonDynamic" format="color"/>
-    <attr name="colorSwitchThumbNormalNonDynamic" format="color"/>
-    <attr name="colorSwitchThumbDisabledNonDynamic" format="color"/>
-    <attr name="colorSwitchTrackNormalNonDynamic" format="color"/>
-
-    <!-- Fixed colors that won't work with dynamic colors. -->
-    <!-- Used in SwitchMaterial thumb tint list for the disabled state. -->
-    <attr name="colorFixedOnSurfaceAlpha38OverSurface" format="color"/>
-
     <!-- Splintered semantic names that support dynamic colors. -->
     <attr name="default_bg_color_dynamic" format="color"/>
     <attr name="divider_line_bg_color_dynamic" format="color"/>
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index de432b6..b390299 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -27,6 +27,7 @@
 
 import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.FileUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
@@ -641,7 +642,6 @@
     private static boolean isUnderAppDir(String path, Context context) {
         File file = new File(path);
         File dataDir = ContextCompat.getDataDir(context);
-
         try {
             String pathCanonical = file.getCanonicalPath();
             String dataDirCanonical = dataDir.getCanonicalPath();
@@ -912,7 +912,9 @@
 
         @Override
         public Boolean doInBackground() {
-            return !isUnderAppDir(mFilePath, mContext);
+            // Don't allow invalid file path or files under app dir to be uploaded.
+            return !isUnderAppDir(mFilePath, mContext)
+                    && !FileUtils.getAbsoluteFilePath(mFilePath).isEmpty();
         }
 
         @Override
diff --git a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
index c078628..e4557c7 100644
--- a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
@@ -7,19 +7,26 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
 
 import android.net.Uri;
 import android.webkit.MimeTypeMap;
 
 import androidx.core.content.ContextCompat;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowMimeTypeMap;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.FileUtils;
+import org.chromium.base.FileUtilsJni;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 
 import java.io.File;
@@ -35,6 +42,14 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class SelectFileDialogTest {
+    @Mock
+    FileUtils.Natives mFileUtilsMocks;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
     /**
      * Returns the determined scope for the accepted |fileTypes|.
      */
@@ -164,6 +179,11 @@
 
     private void testFilePath(
             String path, SelectFileDialog selectFileDialog, boolean expectedPass) {
+        testFilePath(path, selectFileDialog, expectedPass, expectedPass);
+    }
+
+    private void testFilePath(String path, SelectFileDialog selectFileDialog,
+            boolean expectedFileSelectionResult, boolean expectedGetDisplayNameResult) {
         Uri[] uris = new Uri[1];
         uris[0] = Uri.fromFile(new File(path));
 
@@ -172,12 +192,15 @@
         SelectFileDialog.GetDisplayNameTask task2 =
                 selectFileDialog.new GetDisplayNameTask(ContextUtils.getApplicationContext(),
                         /* isMultiple = */ false, uris);
-        assertEquals(expectedPass, task.doInBackground());
-        assertEquals(expectedPass, null != task2.doInBackground());
+        assertEquals(expectedFileSelectionResult, task.doInBackground());
+        assertEquals(expectedGetDisplayNameResult, null != task2.doInBackground());
     }
 
     @Test
     public void testFilePathTasks() throws IOException {
+        FileUtilsJni.TEST_HOOKS.setInstanceForTesting(mFileUtilsMocks);
+        doReturn("/tmp/xyz.jpn").when(mFileUtilsMocks).getAbsoluteFilePath(any());
+
         SelectFileDialog selectFileDialog = new SelectFileDialog(0);
 
         // Obtain the data directory for RoboElectric. It should look something like:
@@ -207,6 +230,11 @@
         // Make sure that dataDir/../dataDir is treated the same as dataDir (and fail the request).
         testFilePath(dataDir + "/../" + lastComponent + "/xyz.jpg", selectFileDialog,
                 /* expectedPass= */ false);
+
+        // Tests invalid file path should fail file selection.
+        doReturn(new String()).when(mFileUtilsMocks).getAbsoluteFilePath(any());
+        testFilePath("\\/tmp/xyz.jpg", selectFileDialog,
+                /* expectedFileSelectionResult= */ false, /* expectedGetDisplayNameResult= */ true);
     }
 
     @Test
diff --git a/ui/base/models/tree_node_model.h b/ui/base/models/tree_node_model.h
index f1c14be..1737bae 100644
--- a/ui/base/models/tree_node_model.h
+++ b/ui/base/models/tree_node_model.h
@@ -9,6 +9,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -17,10 +18,6 @@
 #include "base/observer_list.h"
 #include "ui/base/models/tree_model.h"
 
-namespace bookmarks {
-class BookmarkModel;
-}
-
 namespace ui {
 
 // TreeNodeModel and TreeNodes provide an implementation of TreeModel around
@@ -161,10 +158,33 @@
     return parent_ ? parent_->HasAncestor(ancestor) : false;
   }
 
- private:
-  // TODO(https://crbug.com/956314): Remove this.
-  friend class bookmarks::BookmarkModel;
+  // Reorders children according to a new arbitrary order. |new_order| must
+  // contain one entry per child node, and the value of the entry at position
+  // |i| represents the new position, which must be unique and in the range
+  // between 0 (inclusive) and the number of children (exclusive).
+  void ReorderChildren(const std::vector<size_t>& new_order) {
+    const size_t children_count = children_.size();
+    DCHECK_EQ(children_count, new_order.size());
+    DCHECK_EQ(children_count,
+              std::set(new_order.begin(), new_order.end()).size());
+    TreeNodes new_children(children_count);
+    for (size_t old_index = 0; old_index < children_count; ++old_index) {
+      const size_t new_index = new_order[old_index];
+      DCHECK_LT(new_index, children_count);
+      DCHECK(children_[old_index]);
+      DCHECK(!new_children[new_index]);
+      new_children[new_index] = std::move(children_[old_index]);
+    }
+    children_ = std::move(new_children);
+  }
 
+  // Sorts children according to a comparator.
+  template <typename Compare>
+  void SortChildren(Compare comp) {
+    std::stable_sort(children_.begin(), children_.end(), comp);
+  }
+
+ private:
   // Title displayed in the tree.
   std::u16string title_;
 
diff --git a/ui/base/models/tree_node_model_unittest.cc b/ui/base/models/tree_node_model_unittest.cc
index 7afb9e68..f090989 100644
--- a/ui/base/models/tree_node_model_unittest.cc
+++ b/ui/base/models/tree_node_model_unittest.cc
@@ -285,4 +285,53 @@
   EXPECT_FALSE(child1->is_root());
 }
 
+TEST_F(TreeNodeModelTest, ReorderChildren) {
+  TestNode root;
+
+  TestNode* child0 = root.Add(std::make_unique<TestNode>(), 0);
+  TestNode* child1 = root.Add(std::make_unique<TestNode>(), 1);
+  TestNode* child2 = root.Add(std::make_unique<TestNode>(), 2);
+  TestNode* child3 = root.Add(std::make_unique<TestNode>(), 3);
+
+  ASSERT_EQ(4u, root.children().size());
+  ASSERT_EQ(child0, root.children()[0].get());
+  ASSERT_EQ(child1, root.children()[1].get());
+  ASSERT_EQ(child2, root.children()[2].get());
+  ASSERT_EQ(child3, root.children()[3].get());
+
+  root.ReorderChildren({3, 1, 2, 0});
+
+  ASSERT_EQ(4u, root.children().size());
+  EXPECT_EQ(child3, root.children()[0].get());
+  EXPECT_EQ(child1, root.children()[1].get());
+  EXPECT_EQ(child2, root.children()[2].get());
+  EXPECT_EQ(child0, root.children()[3].get());
+}
+
+TEST_F(TreeNodeModelTest, SortChildren) {
+  TestNode root;
+
+  TestNode* child3 = root.Add(std::make_unique<TestNode>(3), 0);
+  TestNode* child1 = root.Add(std::make_unique<TestNode>(1), 1);
+  TestNode* child2 = root.Add(std::make_unique<TestNode>(2), 2);
+  TestNode* child0 = root.Add(std::make_unique<TestNode>(0), 3);
+
+  ASSERT_EQ(4u, root.children().size());
+  ASSERT_EQ(child3, root.children()[0].get());
+  ASSERT_EQ(child1, root.children()[1].get());
+  ASSERT_EQ(child2, root.children()[2].get());
+  ASSERT_EQ(child0, root.children()[3].get());
+
+  root.SortChildren([](const std::unique_ptr<TestNode>& lhs,
+                       const std::unique_ptr<TestNode>& rhs) {
+    return lhs->value < rhs->value;
+  });
+
+  ASSERT_EQ(4u, root.children().size());
+  EXPECT_EQ(child0, root.children()[0].get());
+  EXPECT_EQ(child1, root.children()[1].get());
+  EXPECT_EQ(child2, root.children()[2].get());
+  EXPECT_EQ(child3, root.children()[3].get());
+}
+
 }  // namespace ui
diff --git a/ui/color/color_id.h b/ui/color/color_id.h
index 8386184..4db2fbf 100644
--- a/ui/color/color_id.h
+++ b/ui/color/color_id.h
@@ -54,8 +54,7 @@
   E_CPONLY(kColorButtonForeground) \
   E_CPONLY(kColorButtonForegroundChecked) \
   E_CPONLY(kColorButtonForegroundDisabled) \
-  E(kColorButtonForegroundProminent, \
-    NativeTheme::kColorId_TextOnProminentButtonColor) \
+  E_CPONLY(kColorButtonForegroundProminent) \
   E_CPONLY(kColorButtonForegroundUnchecked) \
   E_CPONLY(kColorDialogBackground) \
   E_CPONLY(kColorDialogForeground) \
@@ -69,7 +68,7 @@
   E_CPONLY(kColorFrameInactive) \
   E_CPONLY(kColorHelpIconActive) \
   E_CPONLY(kColorHelpIconInactive) \
-  E(kColorIcon, NativeTheme::kColorId_DefaultIconColor) \
+  E_CPONLY(kColorIcon) \
   E_CPONLY(kColorIconDisabled) \
   E_CPONLY(kColorIconSecondary) \
   E_CPONLY(kColorLabelForeground) \
@@ -80,22 +79,21 @@
   E_CPONLY(kColorLinkForeground) \
   E_CPONLY(kColorLinkForegroundDisabled) \
   E_CPONLY(kColorLinkForegroundPressed) \
-  E(kColorMenuBackground, NativeTheme::kColorId_MenuBackgroundColor) \
-  E(kColorTouchableMenuBackground, NativeTheme::kColorId_MenuBackgroundColor) \
+  E_CPONLY(kColorMenuBackground) \
+  E_CPONLY(kColorTouchableMenuBackground) \
   E_CPONLY(kColorMenuBorder) \
   E_CPONLY(kColorMenuDropmarker) \
-  E(kColorMenuIcon, NativeTheme::kColorId_MenuIconColor) \
+  E_CPONLY(kColorMenuIcon) \
   E_CPONLY(kColorMenuItemBackgroundAlertedInitial) \
   E_CPONLY(kColorMenuItemBackgroundAlertedTarget) \
   E_CPONLY(kColorMenuItemBackgroundHighlighted) \
-  E(kColorMenuItemBackgroundSelected, \
-    NativeTheme::kColorId_FocusedMenuItemBackgroundColor) \
+  E_CPONLY(kColorMenuItemBackgroundSelected) \
   E_CPONLY(kColorMenuItemForeground) \
   E_CPONLY(kColorMenuItemForegroundDisabled) \
   E_CPONLY(kColorMenuItemForegroundHighlighted) \
   E_CPONLY(kColorMenuItemForegroundSecondary) \
   E_CPONLY(kColorMenuItemForegroundSelected) \
-  E(kColorMenuSeparator, NativeTheme::kColorId_MenuSeparatorColor) \
+  E_CPONLY(kColorMenuSeparator) \
   E_CPONLY(kColorNotificationActionsBackground) \
   E_CPONLY(kColorNotificationBackgroundActive) \
   E_CPONLY(kColorNotificationBackgroundInactive) \
@@ -180,8 +178,7 @@
   E(kColorWindowBackground, NativeTheme::kColorId_WindowBackground)
 
 #if BUILDFLAG(IS_CHROMEOS)
-#define CHROMEOS_COLOR_IDS \
-  /* ChromeOS native colors */ \
+#define PLATFORM_SPECIFIC_COLOR_IDS \
   E_CPONLY(kColorNativeColor1) \
   E_CPONLY(kColorNativeColor1Shade1) \
   E_CPONLY(kColorNativeColor1Shade2) \
@@ -192,11 +189,8 @@
   E_CPONLY(kColorNativeColor6) \
   E_CPONLY(kColorNativeBaseColor) \
   E_CPONLY(kColorNativeSecondaryColor)
-#endif
-
-#if BUILDFLAG(IS_WIN)
-#define WIN_COLOR_IDS \
-  /* Windows native colors */ \
+#elif BUILDFLAG(IS_WIN)
+#define PLATFORM_SPECIFIC_COLOR_IDS \
   E(kColorNative3dDkShadow, COLOR_3DDKSHADOW) \
   E(kColorNative3dLight, COLOR_3DLIGHT) \
   E(kColorNativeActiveBorder, COLOR_ACTIVEBORDER) \
@@ -227,19 +221,13 @@
   E(kColorNativeWindow, COLOR_WINDOW) \
   E(kColorNativeWindowFrame, COLOR_WINDOWFRAME) \
   E(kColorNativeWindowText, COLOR_WINDOWTEXT)
+#else
+#define PLATFORM_SPECIFIC_COLOR_IDS
 #endif
 
-#if BUILDFLAG(IS_WIN)
 #define COLOR_IDS \
   CROSS_PLATFORM_COLOR_IDS \
-  WIN_COLOR_IDS
-#elif BUILDFLAG(IS_CHROMEOS)
-#define COLOR_IDS \
-  CROSS_PLATFORM_COLOR_IDS \
-  CHROMEOS_COLOR_IDS
-#else
-#define COLOR_IDS CROSS_PLATFORM_COLOR_IDS
-#endif
+  PLATFORM_SPECIFIC_COLOR_IDS
 // clang-format on
 
 namespace ui {
diff --git a/ui/color/win/native_color_mixers_win.cc b/ui/color/win/native_color_mixers_win.cc
index 828d786..32c2741 100644
--- a/ui/color/win/native_color_mixers_win.cc
+++ b/ui/color/win/native_color_mixers_win.cc
@@ -45,7 +45,7 @@
     return;
 
 #define E(chrome, native) {chrome, color_utils::GetSysSkColor(native)},
-  mixer.AddSet({kColorSetNative, {WIN_COLOR_IDS}});
+  mixer.AddSet({kColorSetNative, {PLATFORM_SPECIFIC_COLOR_IDS}});
 #undef E
 
   // Window Background
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index 3c58e65d..7df5959 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -953,11 +953,6 @@
   active_selection_fg_color_ =
       color_provider->GetColor(ui::kColorTextfieldSelectionForeground);
 
-  colors_[ThemeProperties::COLOR_TAB_THROBBER_SPINNING] =
-      color_provider->GetColor(ui::kColorThrobber);
-  colors_[ThemeProperties::COLOR_TAB_THROBBER_WAITING] =
-      color_provider->GetColor(ui::kColorThrobberPreconnect);
-
   // Generate colors that depend on whether or not a custom window frame is
   // used.  These colors belong in |color_map| below, not |colors_|.
   for (bool custom_frame : {false, true}) {
diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc
index e798a31..4657731 100644
--- a/ui/native_theme/common_theme.cc
+++ b/ui/native_theme/common_theme.cc
@@ -31,9 +31,6 @@
     NativeTheme::ColorId color_id,
     NativeTheme::ColorScheme color_scheme) {
   switch (color_id) {
-    case NativeTheme::kColorId_MenuSeparatorColor:
-      return color_scheme == NativeTheme::ColorScheme::kDark ? SK_ColorWHITE
-                                                             : SK_ColorBLACK;
     case NativeTheme::kColorId_FocusedBorderColor:
     case NativeTheme::kColorId_ProminentButtonColor:
       return color_scheme == NativeTheme::ColorScheme::kDark
@@ -47,12 +44,8 @@
 absl::optional<SkColor> GetDarkSchemeColor(NativeTheme::ColorId color_id,
                                            const NativeTheme* base_theme) {
   switch (color_id) {
-    case NativeTheme::kColorId_DefaultIconColor:
-      return gfx::kGoogleGrey500;
     case NativeTheme::kColorId_FocusedBorderColor:
       return gfx::kGoogleBlue400;
-    case NativeTheme::kColorId_MenuSeparatorColor:
-      return gfx::kGoogleGrey800;
     case NativeTheme::kColorId_ProminentButtonColor:
       return gfx::kGoogleBlue300;
     case NativeTheme::kColorId_WindowBackground:
@@ -71,33 +64,9 @@
       return gfx::kGoogleBlue500;
 
     // Button
-    case NativeTheme::kColorId_TextOnProminentButtonColor:
-      return color_utils::GetColorWithMaxContrast(
-          base_theme->GetUnprocessedSystemColor(
-              NativeTheme::kColorId_ProminentButtonColor, color_scheme));
     case NativeTheme::kColorId_ProminentButtonColor:
       return gfx::kGoogleBlue600;
 
-    // Icon
-    case NativeTheme::kColorId_DefaultIconColor:
-      return gfx::kGoogleGrey700;
-
-    // Menu
-    case NativeTheme::kColorId_MenuBackgroundColor:
-      return base_theme->GetUnprocessedSystemColor(
-          NativeTheme::kColorId_WindowBackground, color_scheme);
-    case NativeTheme::kColorId_MenuSeparatorColor:
-      return gfx::kGoogleGrey300;
-    case NativeTheme::kColorId_FocusedMenuItemBackgroundColor: {
-      const SkColor bg = base_theme->GetUnprocessedSystemColor(
-          NativeTheme::kColorId_WindowBackground, color_scheme);
-      const SkColor fg = color_utils::GetColorWithMaxContrast(bg);
-      return color_utils::AlphaBlend(fg, bg, gfx::kGoogleGreyAlpha200);
-    }
-    case NativeTheme::kColorId_MenuIconColor:
-      return base_theme->GetUnprocessedSystemColor(
-          NativeTheme::kColorId_DefaultIconColor, color_scheme);
-
     // Throbber
     case NativeTheme::kColorId_ThrobberWaitingColor: {
       const SkColor bg = base_theme->GetUnprocessedSystemColor(
diff --git a/ui/native_theme/native_theme_color_id.h b/ui/native_theme/native_theme_color_id.h
index e53194c..92787e1f 100644
--- a/ui/native_theme/native_theme_color_id.h
+++ b/ui/native_theme/native_theme_color_id.h
@@ -8,14 +8,8 @@
 // Clang format mangles lists like the below badly.
 // clang-format off
 #define NATIVE_THEME_CROSS_PLATFORM_COLOR_IDS                                  \
-  OP(kColorId_DefaultIconColor),                                               \
   OP(kColorId_FocusedBorderColor),                                             \
-  OP(kColorId_FocusedMenuItemBackgroundColor),                                 \
-  OP(kColorId_MenuBackgroundColor),                                            \
-  OP(kColorId_MenuIconColor),                                                  \
-  OP(kColorId_MenuSeparatorColor),                                             \
   OP(kColorId_ProminentButtonColor),                                           \
-  OP(kColorId_TextOnProminentButtonColor),                                     \
   OP(kColorId_ThrobberSpinningColor),                                          \
   OP(kColorId_ThrobberWaitingColor),                                           \
   OP(kColorId_WindowBackground)
diff --git a/ui/native_theme/native_theme_mac.mm b/ui/native_theme/native_theme_mac.mm
index a766a9c8..8ecee23a 100644
--- a/ui/native_theme/native_theme_mac.mm
+++ b/ui/native_theme/native_theme_mac.mm
@@ -147,16 +147,6 @@
 SkColor NativeThemeMac::GetSystemColorDeprecated(ColorId color_id,
                                                  ColorScheme color_scheme,
                                                  bool apply_processing) const {
-  if (GetPreferredContrast() == PreferredContrast::kMore) {
-    switch (color_id) {
-      case kColorId_FocusedMenuItemBackgroundColor:
-        return color_scheme == ColorScheme::kDark ? SK_ColorLTGRAY
-                                                  : SK_ColorDKGRAY;
-      default:
-        break;
-    }
-  }
-
   absl::optional<SkColor> os_color = GetOSColor(color_id, color_scheme);
   if (os_color.has_value())
     return os_color.value();
@@ -176,11 +166,6 @@
   // Even with --secondary-ui-md, menus use the platform colors and styling, and
   // Mac has a couple of specific color overrides, documented below.
   switch (color_id) {
-    case kColorId_MenuSeparatorColor:
-      return color_scheme == ColorScheme::kDark
-                 ? SkColorSetA(gfx::kGoogleGrey800, 0xCC)
-                 : SkColorSetA(SK_ColorBLACK, 0x26);
-
     case kColorId_FocusedBorderColor:
       return SkColorSetA(
           skia::NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]),
diff --git a/ui/native_theme/native_theme_utils.cc b/ui/native_theme/native_theme_utils.cc
index 31e321a..a5667e0 100644
--- a/ui/native_theme/native_theme_utils.cc
+++ b/ui/native_theme/native_theme_utils.cc
@@ -45,17 +45,9 @@
   using NTCID = NativeTheme::ColorId;
   static constexpr const auto map =
       base::MakeFixedFlatMap<NativeTheme::ColorId, ColorId>({
-        {NTCID::kColorId_DefaultIconColor, kColorIcon},
         {NTCID::kColorId_FocusedBorderColor, kColorFocusableBorderFocused},
-        {NTCID::kColorId_FocusedMenuItemBackgroundColor,
-          kColorMenuItemBackgroundSelected},
-        {NTCID::kColorId_MenuBackgroundColor, kColorMenuBackground},
-        {NTCID::kColorId_MenuIconColor, kColorMenuIcon},
-        {NTCID::kColorId_MenuSeparatorColor, kColorMenuSeparator},
         {NTCID::kColorId_ProminentButtonColor,
           kColorButtonBackgroundProminent},
-        {NTCID::kColorId_TextOnProminentButtonColor,
-          kColorButtonForegroundProminent},
         {NTCID::kColorId_ThrobberSpinningColor, kColorThrobber},
         {NTCID::kColorId_ThrobberWaitingColor, kColorThrobberPreconnect},
         {NTCID::kColorId_WindowBackground, kColorWindowBackground},
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index e30e7a2..0f2d9333 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -619,28 +619,18 @@
     case kColorId_WindowBackground:
       return system_colors_[SystemThemeColor::kWindow];
 
-    case kColorId_MenuIconColor:
     case kColorId_ThrobberSpinningColor:
-    case kColorId_DefaultIconColor:
       return system_colors_[SystemThemeColor::kWindowText];
 
     case kColorId_ThrobberWaitingColor:
       return system_colors_[SystemThemeColor::kGrayText];
 
-    case kColorId_MenuBackgroundColor:
-      return system_colors_[SystemThemeColor::kButtonFace];
-
-    case kColorId_MenuSeparatorColor:
     case kColorId_FocusedBorderColor:
       return system_colors_[SystemThemeColor::kButtonText];
 
     case kColorId_ProminentButtonColor:
-    case kColorId_FocusedMenuItemBackgroundColor:
       return system_colors_[SystemThemeColor::kHighlight];
 
-    case kColorId_TextOnProminentButtonColor:
-      return system_colors_[SystemThemeColor::kHighlightText];
-
     default:
       return absl::nullopt;
   }
diff --git a/ui/ozone/common/features.cc b/ui/ozone/common/features.cc
index 8f12b54..3b5015f 100644
--- a/ui/ozone/common/features.cc
+++ b/ui/ozone/common/features.cc
@@ -16,9 +16,13 @@
 // to properly composite the buffers. This mode is used to support fractional
 // scale factor.
 const base::Feature kWaylandSurfaceSubmissionInPixelCoordinates{
-    "WaylandSurfaceSubmissionInPixelCoordinates",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
+  "WaylandSurfaceSubmissionInPixelCoordinates",
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 bool IsWaylandSurfaceSubmissionInPixelCoordinatesEnabled() {
   return base::FeatureList::IsEnabled(
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
index 47f7810a..42f7dd1e 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
@@ -241,6 +241,14 @@
     }
   }
 
+  disconnectedCallback() {
+    super.disconnectedCallback();
+
+    if (this.bluetoothDiscoveryDelegateReceiver_) {
+      this.bluetoothDiscoveryDelegateReceiver_.$.close();
+    }
+  }
+
   /** @override */
   onPropertiesUpdated(properties) {
     const wasBluetoothEnabled = this.isBluetoothEnabled_;