diff --git a/DEPS b/DEPS
index 03e9d975..5046abc 100644
--- a/DEPS
+++ b/DEPS
@@ -306,15 +306,15 @@
   # 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': 'b0e0442cd021833f02a6bd92f3e2e2a172818b43',
+  'skia_revision': 'b5b35f8dc919376c000feb1c7c7176fd0cc7b3de',
   # 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': '18e4c7416a6f4bdc5b825f9c3257edbafa7cd730',
+  'v8_revision': '1e3c2d12fd42ec18fc0bbb19a97e728941e6c4c8',
   # 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': 'f37a32f0701e948b5180cd0b6af79acfef290a43',
+  'angle_revision': '356b2a590e11be18482696ea1d283e975fd612d2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -329,11 +329,11 @@
   #
   # Note this revision should be updated with
   # third_party/boringssl/roll_boringssl.py, not roll-dep.
-  'boringssl_revision': '1ee71185a2322dc354bee5e5a0abfb1810a27dc6',
+  'boringssl_revision': 'f0518d45119dd4dd322a884669daf8247bc3c992',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:10.20221121.0.1',
+  'fuchsia_version': 'version:10.20221121.2.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -377,7 +377,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'b898eb3e06f5f67172dfbdc19e1451d52638dbd4',
+  'catapult_revision': '39d570c940b94f4179fa345573355455ffb4ab64',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -421,7 +421,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '95df1efa5a7521614907b3c7200c3a1a4ab3127d',
+  'dawn_revision': 'f5eec817de0df46d5aa2a7205f5b2f9c02e98dd4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -816,7 +816,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '19d35b9fd53210fb5cf1af9d6d32929136a060ad',
+    '709b59b8dbb560a3d544b4ddea303fd2b5a13a72',
     'condition': 'checkout_android and checkout_src_internal and not checkout_clank_via_src_internal',
   },
 
@@ -910,7 +910,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': '0Spv3S8B9GHYWht5RMKVErkK8uMA87ASY076aOTgRVwC',
+          'version': 'xB-0wbx4vRW3qCqhlfnA7s8p91c-ud-RnG8CiPoqimgC',
         },
       ],
       'dep_type': 'cipd',
@@ -932,7 +932,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': '2ASyp7Dyws79jFos8NF-oCBCc_GW2GwNKUEdk31pqiQC',
+          'version': 'cfeCqvGAqJxTQDu6lW0DhLdZFtE0dU4Fm4c81E_F_FYC',
         },
       ],
       'dep_type': 'cipd',
@@ -1000,7 +1000,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'QywWz1_m6w-DIah4IXQ3I27LDjKgn9kUMTP1SEJgigUC',
+          'version': 'QQCBJDbCcTJd-GDm4CHpjKdcXKjbHP9ebzyHDuU2x-4C',
       },
     ],
     'condition': 'checkout_android',
@@ -1215,7 +1215,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7ce0019ef66b5c970850870b65100b76cae15a47',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '06404255922ceb03054273b5c35fe2b67cea41f4',
       'condition': 'checkout_chromeos',
   },
 
@@ -1534,7 +1534,7 @@
     Var('chromium_git') + '/webm/libwebp.git' + '@' +  '7366f7f394af26de814296152c50e673ed0a832f',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + 'fe9ced6e3c8ae6c69bcc3ebb8505a650d2df30e0',
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '8713ba3f0bddfa19943559981acd5aad2d703d5d',
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -1623,7 +1623,7 @@
     Var('chromium_git') + '/external/github.com/intel/ARM_NEON_2_x86_SSE.git' + '@' + 'a15b489e1222b2087007546b4912e21293ea86ff',
 
   'src/third_party/netty-tcnative/src': {
-      'url': Var('chromium_git') + '/external/netty-tcnative.git' + '@' + '7eeb50be90c9ba0f6afa3375132df63942a0f32d',
+      'url': Var('chromium_git') + '/external/netty-tcnative.git' + '@' + '7b6c4ac18e823ba463085d1e44968773e2a82c02',
       'condition': 'checkout_android',
   },
 
@@ -1664,7 +1664,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd8f0dc3d20c814236d97a042f06ffacd8f2d6b1f',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f5b6c4ed868219aefe172ac2d5bd683814a5f001',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1704,7 +1704,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'vqsrb5_6fg9u-aSAqrcJiG7q2GOG66_39vdYTKgHmHgC',
+              'version': 'zJXokrYtEiaa-jjRGetxUuWf3Zkv_G7Fvl5oCXkhBAsC',
           },
       ],
       'condition': 'checkout_android',
@@ -1919,7 +1919,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@84f51a9106fa52bd9dfffcc13df752839dc93dc8',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7a06af5537b39890c9d465509667116ad8b3aaf4',
     'condition': 'checkout_src_internal',
   },
 
@@ -1960,7 +1960,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'UUYSYs5_Q0QL4-I974AWD-A4ETSwXD9bmrOHP99vO1AC',
+        'version': 'dPtbu2LkFG30wC5QbPnPVuVRjP7mZG6gadfnS2XzVqcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/expectations/system_webview_bundle.AndroidManifest.expected b/android_webview/expectations/system_webview_bundle.AndroidManifest.expected
index 8c2eda2..b18b638 100644
--- a/android_webview/expectations/system_webview_bundle.AndroidManifest.expected
+++ b/android_webview/expectations/system_webview_bundle.AndroidManifest.expected
@@ -14,7 +14,7 @@
   <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33"/>
   <application
       android:name="org.chromium.android_webview.nonembedded.WebViewApkApplication"
-      android:extractNativeLibs="True"
+      android:extractNativeLibs="false"
       android:icon="@$PACKAGE:drawable/icon_webview"
       android:label="Android System WebView"
       android:multiArch="true"
diff --git a/android_webview/expectations/trichrome_webview_bundle.AndroidManifest.expected b/android_webview/expectations/trichrome_webview_bundle.AndroidManifest.expected
index 8d7414d1..2c7b74ca5 100644
--- a/android_webview/expectations/trichrome_webview_bundle.AndroidManifest.expected
+++ b/android_webview/expectations/trichrome_webview_bundle.AndroidManifest.expected
@@ -14,7 +14,7 @@
   <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="33"/>
   <application
       android:name="org.chromium.android_webview.nonembedded.WebViewApkApplication"
-      android:extractNativeLibs="False"
+      android:extractNativeLibs="false"
       android:icon="@$PACKAGE:drawable/icon_webview"
       android:label="Android System WebView"
       android:multiArch="true"
diff --git a/android_webview/nonembedded/java/AndroidManifest.xml b/android_webview/nonembedded/java/AndroidManifest.xml
index f31a77f8f..a6ec0a7 100644
--- a/android_webview/nonembedded/java/AndroidManifest.xml
+++ b/android_webview/nonembedded/java/AndroidManifest.xml
@@ -38,7 +38,7 @@
                  android:name="{{ application_name|default('org.chromium.android_webview.nonembedded.WebViewApkApplication') }}"
                  android:multiArch="true"
                  {{ use32bitAbi|default('android:use32bitAbi="true"') }}
-                 android:extractNativeLibs="{{ trichrome_library is not defined }}">
+                 android:extractNativeLibs="false">
         {# This part is shared between stand-alone WebView and Monochrome #}
         {% macro common(manifest_package, webview_lib) %}
             <meta-data android:name="com.android.webview.WebViewLibrary"
diff --git a/android_webview/system_webview_apk_tmpl.gni b/android_webview/system_webview_apk_tmpl.gni
index 3d60cf9..52dca0a 100644
--- a/android_webview/system_webview_apk_tmpl.gni
+++ b/android_webview/system_webview_apk_tmpl.gni
@@ -140,6 +140,7 @@
     _include_primary_support = false
     _include_secondary_support = false
 
+    uncompress_shared_libraries = true
     if (!_use_trichrome_library) {
       shared_resources = true
 
@@ -154,7 +155,6 @@
       }
       deps += [ "//third_party/icu:icu_assets" ]
     } else {
-      uncompress_shared_libraries = true
       app_as_shared_lib = true
 
       # Include placeholder libraries to ensure we are treated as the desired
diff --git a/ash/clipboard/views/clipboard_history_item_view.cc b/ash/clipboard/views/clipboard_history_item_view.cc
index 085ac69..460fc77 100644
--- a/ash/clipboard/views/clipboard_history_item_view.cc
+++ b/ash/clipboard/views/clipboard_history_item_view.cc
@@ -45,10 +45,11 @@
 void ClipboardHistoryItemView::ContentsView::OnHostPseudoFocusUpdated() {
   delete_button_->SetVisible(container_->ShouldShowDeleteButton());
 
-  const bool focused =
-      (container_->pseudo_focus_ == PseudoFocus::kDeleteButton);
-  views::InkDrop::Get(delete_button_)->GetInkDrop()->SetFocused(focused);
-  if (focused) {
+  const bool delete_button_focused = container_->IsDeleteButtonPseudoFocused();
+  views::InkDrop::Get(delete_button_)
+      ->GetInkDrop()
+      ->SetFocused(delete_button_focused);
+  if (delete_button_focused) {
     delete_button_->NotifyAccessibilityEvent(ax::mojom::Event::kHover,
                                              /*send_native_event*/ true);
   }
@@ -108,13 +109,12 @@
 
   // When the menu item is disabled, only the delete button is able to work.
   if (!container_->GetEnabled()) {
-    DCHECK_EQ(PseudoFocus::kDeleteButton, pseudo_focus_);
+    DCHECK(IsDeleteButtonPseudoFocused());
     SetPseudoFocus(PseudoFocus::kEmpty);
     return false;
   }
 
-  DCHECK(pseudo_focus_ == PseudoFocus::kMainButton ||
-         pseudo_focus_ == PseudoFocus::kDeleteButton);
+  DCHECK(IsMainButtonPseudoFocused() || IsDeleteButtonPseudoFocused());
   int new_pseudo_focus = pseudo_focus_;
   bool move_focus_out = false;
   if (reverse) {
@@ -229,10 +229,14 @@
   InitiatePseudoFocus(/*reverse=*/false);
 }
 
-bool ClipboardHistoryItemView::ShouldHighlight() const {
+bool ClipboardHistoryItemView::IsMainButtonPseudoFocused() const {
   return pseudo_focus_ == PseudoFocus::kMainButton;
 }
 
+bool ClipboardHistoryItemView::IsDeleteButtonPseudoFocused() const {
+  return pseudo_focus_ == PseudoFocus::kDeleteButton;
+}
+
 void ClipboardHistoryItemView::OnMouseClickOnDescendantCanceled() {
   // When mouse click is canceled, mouse may hover a different menu item from
   // the one where the click event started. A typical way is to move the mouse
@@ -271,6 +275,12 @@
   // via AXNodeData::SetName.
   data->role = ax::mojom::Role::kMenuItem;
   data->SetNameChecked(GetAccessibleName());
+
+  // In fitting with existing conventions for menu items, we treat clipboard
+  // history items as "selected" from an accessibility standpoint if pressing
+  // Enter will perform the item's default expected action: pasting.
+  data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
+                         IsMainButtonPseudoFocused());
 }
 
 void ClipboardHistoryItemView::Activate(Action action, int event_flags) {
@@ -308,19 +318,14 @@
 }
 
 bool ClipboardHistoryItemView::ShouldShowDeleteButton() const {
-  return (pseudo_focus_ == PseudoFocus::kMainButton && IsMouseHovered()) ||
-         pseudo_focus_ == PseudoFocus::kDeleteButton ||
-         under_gesture_long_press_;
+  return (IsMainButtonPseudoFocused() && IsMouseHovered()) ||
+         IsDeleteButtonPseudoFocused() || under_gesture_long_press_;
 }
 
 void ClipboardHistoryItemView::InitiatePseudoFocus(bool reverse) {
-  PseudoFocus target_pseudo_focus;
-  if (!container_->GetEnabled() || reverse)
-    target_pseudo_focus = PseudoFocus::kDeleteButton;
-  else
-    target_pseudo_focus = PseudoFocus::kMainButton;
-
-  SetPseudoFocus(target_pseudo_focus);
+  SetPseudoFocus(reverse || !container_->GetEnabled()
+                     ? PseudoFocus::kDeleteButton
+                     : PseudoFocus::kMainButton);
 }
 
 void ClipboardHistoryItemView::SetPseudoFocus(PseudoFocus new_pseudo_focus) {
@@ -329,7 +334,7 @@
     return;
 
   pseudo_focus_ = new_pseudo_focus;
-  if (pseudo_focus_ == PseudoFocus::kMainButton) {
+  if (IsMainButtonPseudoFocused()) {
     NotifyAccessibilityEvent(ax::mojom::Event::kSelection,
                              /*send_native_event=*/true);
   }
diff --git a/ash/clipboard/views/clipboard_history_item_view.h b/ash/clipboard/views/clipboard_history_item_view.h
index 13177bb..9134d73 100644
--- a/ash/clipboard/views/clipboard_history_item_view.h
+++ b/ash/clipboard/views/clipboard_history_item_view.h
@@ -5,6 +5,7 @@
 #ifndef ASH_CLIPBOARD_VIEWS_CLIPBOARD_HISTORY_ITEM_VIEW_H_
 #define ASH_CLIPBOARD_VIEWS_CLIPBOARD_HISTORY_ITEM_VIEW_H_
 
+#include "ash/ash_export.h"
 #include "ash/clipboard/clipboard_history_util.h"
 #include "ui/views/view.h"
 #include "ui/views/view_targeter_delegate.h"
@@ -20,7 +21,7 @@
 class ClipboardHistoryResourceManager;
 
 // The base class for menu items of the clipboard history menu.
-class ClipboardHistoryItemView : public views::View {
+class ASH_EXPORT ClipboardHistoryItemView : public views::View {
  public:
   static std::unique_ptr<ClipboardHistoryItemView>
   CreateFromClipboardHistoryItem(
@@ -50,8 +51,15 @@
   // Called when the selection state has changed.
   void OnSelectionChanged();
 
-  // Returns whether the highlight background should show.
-  bool ShouldHighlight() const;
+  // Returns whether the item's main button has pseudo focus, meaning the item's
+  // contents will be pasted if the user presses Enter. An item's background is
+  // highlighted when its main button has pseudo focus.
+  bool IsMainButtonPseudoFocused() const;
+
+  // Returns whether the item's delete button has pseudo focus, meaning the item
+  // will be removed from clipboard history if the user presses Enter. An item's
+  // background is not highlighted when its delete button has pseudo focus.
+  bool IsDeleteButtonPseudoFocused() const;
 
   // Called when the mouse click on descendants (such as the main button or
   // the delete button) gets canceled.
diff --git a/ash/clipboard/views/clipboard_history_main_button.cc b/ash/clipboard/views/clipboard_history_main_button.cc
index 2c0bbd0b..72daa37 100644
--- a/ash/clipboard/views/clipboard_history_main_button.cc
+++ b/ash/clipboard/views/clipboard_history_main_button.cc
@@ -55,7 +55,7 @@
 ClipboardHistoryMainButton::~ClipboardHistoryMainButton() = default;
 
 void ClipboardHistoryMainButton::OnHostPseudoFocusUpdated() {
-  SetShouldHighlight(container_->ShouldHighlight());
+  SetShouldHighlight(container_->IsMainButtonPseudoFocused());
 }
 
 void ClipboardHistoryMainButton::SetShouldHighlight(bool should_highlight) {
diff --git a/ash/webui/shortcut_customization_ui/backend/BUILD.gn b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
index e6227c6d..d86bf626 100644
--- a/ash/webui/shortcut_customization_ui/backend/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
@@ -10,6 +10,7 @@
   sources = [
     "accelerator_configuration_provider.cc",
     "accelerator_configuration_provider.h",
+    "shortcut_customization_delegate.h",
   ]
 
   deps = [
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
index fbf51cb..c2c39a5 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
@@ -4,6 +4,8 @@
 
 #include "ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h"
 
+#include <memory>
+#include <utility>
 #include <vector>
 
 #include "ash/accelerators/accelerator_layout_table.h"
@@ -12,6 +14,7 @@
 #include "ash/public/cpp/accelerators_util.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h"
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
 #include "base/containers/fixed_flat_map.h"
 #include "base/containers/flat_map.h"
@@ -132,9 +135,13 @@
 
 namespace shortcut_ui {
 
-AcceleratorConfigurationProvider::AcceleratorConfigurationProvider()
+AcceleratorConfigurationProvider::AcceleratorConfigurationProvider(
+    std::unique_ptr<ShortcutCustomizationDelegate>
+        shortcut_customization_delegate)
     : ash_accelerator_configuration_(
-          Shell::Get()->ash_accelerator_configuration()) {
+          Shell::Get()->ash_accelerator_configuration()),
+      shortcut_customization_delegate_(
+          std::move(shortcut_customization_delegate)) {
   // Observe connected keyboard events.
   ui::DeviceDataManager::GetInstance()->AddObserver(this);
 
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
index c5d9927..5187b42b 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
@@ -5,6 +5,8 @@
 #ifndef ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_BACKEND_ACCELERATOR_CONFIGURATION_PROVIDER_H_
 #define ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_BACKEND_ACCELERATOR_CONFIGURATION_PROVIDER_H_
 
+#include <memory>
+
 #include "ash/public/cpp/accelerator_configuration.h"
 #include "ash/public/mojom/accelerator_keys.mojom.h"
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
@@ -20,6 +22,8 @@
 namespace ash {
 namespace shortcut_ui {
 
+class ShortcutCustomizationDelegate;
+
 class AcceleratorConfigurationProvider
     : public shortcut_customization::mojom::AcceleratorConfigurationProvider,
       public ui::InputDeviceEventObserver,
@@ -33,7 +37,9 @@
       mojom::AcceleratorSource,
       std::map<AcceleratorActionId, std::vector<ui::Accelerator>>>;
 
-  AcceleratorConfigurationProvider();
+  explicit AcceleratorConfigurationProvider(
+      std::unique_ptr<ShortcutCustomizationDelegate>
+          shortcut_customization_delegate);
   AcceleratorConfigurationProvider(const AcceleratorConfigurationProvider&) =
       delete;
   AcceleratorConfigurationProvider& operator=(
@@ -117,6 +123,11 @@
   mojo::Remote<shortcut_customization::mojom::AcceleratorsUpdatedObserver>
       accelerators_updated_observers_;
 
+  // Provides browser functionality from //chrome to the Shortcut Customization
+  // UI.
+  std::unique_ptr<ShortcutCustomizationDelegate>
+      shortcut_customization_delegate_;
+
   base::WeakPtrFactory<AcceleratorConfigurationProvider> weak_ptr_factory_{
       this};
 };
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index 7e203f0..4548fd36 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -17,6 +17,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h"
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
 #include "base/bind.h"
 #include "base/callback_forward.h"
@@ -164,6 +165,14 @@
 
 namespace shortcut_ui {
 
+class FakeShortcutCustomizationDelegate : public ShortcutCustomizationDelegate {
+ public:
+  FakeShortcutCustomizationDelegate() = default;
+  ~FakeShortcutCustomizationDelegate() override = default;
+
+  PrefService* GetPrefService() override { return nullptr; }
+};
+
 class AcceleratorConfigurationProviderTest : public AshTestBase {
  public:
   AcceleratorConfigurationProviderTest() = default;
@@ -203,7 +212,8 @@
     AshTestSuite::LoadTestResources();
     AshTestBase::SetUp();
 
-    provider_ = std::make_unique<AcceleratorConfigurationProvider>();
+    provider_ = std::make_unique<AcceleratorConfigurationProvider>(
+        std::make_unique<FakeShortcutCustomizationDelegate>());
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h b/ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h
new file mode 100644
index 0000000..cb0597d
--- /dev/null
+++ b/ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h
@@ -0,0 +1,24 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_BACKEND_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
+#define ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_BACKEND_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
+
+#include "components/prefs/pref_service.h"
+
+namespace ash::shortcut_ui {
+
+// A delegate which exposes browser functionality from //chrome to the Shortcut
+// Customization UI.
+class ShortcutCustomizationDelegate {
+ public:
+  virtual ~ShortcutCustomizationDelegate() = default;
+
+  // Get the pref service.
+  virtual PrefService* GetPrefService() = 0;
+};
+
+}  // namespace ash::shortcut_ui
+
+#endif  // ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_BACKEND_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
\ No newline at end of file
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.html b/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.html
index f9ff33a..04b2804 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.html
@@ -13,11 +13,12 @@
 <div id="container" part="container">
   <div id="title">[[title]]</div>
   <div id="rowList">
-    <template id="list" is="dom-repeat" items="[[acceleratorContainer]]">
-      <accelerator-row class="acceleratorRow" description="[[item.description]]"
-          action="[[item.action]]"
-          source="[[item.source]]"
-          accelerator-infos="[[item.acceleratorInfos]]">
+    <template id="list" is="dom-repeat" items="[[accelRowDataArray]]">
+      <accelerator-row
+          accelerator-infos="[[item.acceleratorInfos]]"
+          action="[[item.layoutInfo.action]]"
+          description="[[item.layoutInfo.description]]"
+          source="[[item.layoutInfo.source]]">
       </accelerator-row>
     </template>
   </div>
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.ts
index fac1f00e..f694a56d 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_subsection.ts
@@ -1,4 +1,4 @@
-// Copyright 2021 The Chromium Authors
+// Copyright 2022 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,13 +9,15 @@
 import {AcceleratorLookupManager} from './accelerator_lookup_manager.js';
 import {getTemplate} from './accelerator_subsection.html.js';
 import {fakeSubCategories} from './fake_data.js';
-import {AcceleratorCategory, AcceleratorInfo, AcceleratorState, AcceleratorSubcategory, AcceleratorType} from './shortcut_types.js';
+import {AcceleratorCategory, AcceleratorInfo, AcceleratorState, AcceleratorSubcategory, AcceleratorType, LayoutInfo} from './shortcut_types.js';
 
-export interface Accelerator {
-  description: string;
-  action: number;
-  source: number;
+/**
+ * This interface is used to hold all the data needed by an
+ * AcceleratorRowElement.
+ */
+interface AcceleratorRowData {
   acceleratorInfos: AcceleratorInfo[];
+  layoutInfo: LayoutInfo;
 }
 
 export interface AcceleratorSubsectionElement {
@@ -67,7 +69,7 @@
   override title: string;
   category: AcceleratorCategory;
   subcategory: AcceleratorSubcategory;
-  acceleratorContainer: Accelerator[];
+  accelRowDataArray: AcceleratorRowData[];
   private lookupManager_: AcceleratorLookupManager =
       AcceleratorLookupManager.getInstance();
 
@@ -98,26 +100,23 @@
     // updates as one which results in strange behaviors with updating
     // individual subsections. An atomic replacement makes ensures each
     // subsection's accelerators are kept distinct from each other.
-    const tempAccelContainer: Accelerator[] = [];
-    layoutInfos!.forEach((value) => {
-      const acceleratorInfos =
-          this.lookupManager_.getAcceleratorInfos(value.source, value.action);
-      acceleratorInfos!.filter((accel) => {
+    const tempAccelRowData: AcceleratorRowData[] = [];
+    layoutInfos!.forEach((layoutInfo) => {
+      const acceleratorInfos = this.lookupManager_.getAcceleratorInfos(
+          layoutInfo.source, layoutInfo.action);
+      acceleratorInfos.filter((accel) => {
         // Hide accelerators that are default and disabled.
         return !(
             accel.type === AcceleratorType.kDefault &&
             accel.state === AcceleratorState.kDisabledByUser);
       });
-      const accel: Accelerator = {
-        description:
-            this.lookupManager_.getAcceleratorName(value.source, value.action),
-        action: value.action,
-        source: value.source,
-        acceleratorInfos: acceleratorInfos!,
+      const accelRowData: AcceleratorRowData = {
+        layoutInfo,
+        acceleratorInfos,
       };
-      tempAccelContainer.push(accel);
+      tempAccelRowData.push(accelRowData);
     });
-    this.acceleratorContainer = tempAccelContainer;
+    this.accelRowDataArray = tempAccelRowData;
   }
 
   static get template() {
diff --git a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
index 22168dab..8ef7fdc0 100644
--- a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
+++ b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
@@ -10,6 +10,7 @@
 #include "ash/webui/grit/ash_shortcut_customization_app_resources.h"
 #include "ash/webui/grit/ash_shortcut_customization_app_resources_map.h"
 #include "ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h"
+#include "ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h"
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
 #include "ash/webui/shortcut_customization_ui/url_constants.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
@@ -54,7 +55,10 @@
 
 }  // namespace
 
-ShortcutCustomizationAppUI::ShortcutCustomizationAppUI(content::WebUI* web_ui)
+ShortcutCustomizationAppUI::ShortcutCustomizationAppUI(
+    content::WebUI* web_ui,
+    std::unique_ptr<shortcut_ui::ShortcutCustomizationDelegate>
+        shortcut_customization_delegate)
     : ui::MojoWebUIController(web_ui) {
   content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
       web_ui->GetWebContents()->GetBrowserContext(),
@@ -75,7 +79,10 @@
 
   AddFeatureFlags(source);
 
-  provider_ = std::make_unique<shortcut_ui::AcceleratorConfigurationProvider>();
+  // TODO(longbowei): Use GetPrefService() to get pref_service and
+  // add it as a param for AcceleratorConfigurationProvider.
+  provider_ = std::make_unique<shortcut_ui::AcceleratorConfigurationProvider>(
+      std::move(shortcut_customization_delegate));
 }
 
 ShortcutCustomizationAppUI::~ShortcutCustomizationAppUI() = default;
diff --git a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
index b3753ef..45ee5ffa 100644
--- a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
+++ b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
@@ -20,20 +20,29 @@
 
 namespace ash {
 
+namespace shortcut_ui {
+class ShortcutCustomizationDelegate;
+}  // namespace shortcut_ui
+
 class ShortcutCustomizationAppUI;
 
 // The WebUIConfig for chrome://shortcut-customization.
 class ShortcutCustomizationAppUIConfig
     : public SystemWebAppUIConfig<ShortcutCustomizationAppUI> {
  public:
-  ShortcutCustomizationAppUIConfig()
+  explicit ShortcutCustomizationAppUIConfig(
+      SystemWebAppUIConfig::CreateWebUIControllerFunc create_controller_func)
       : SystemWebAppUIConfig(kChromeUIShortcutCustomizationAppHost,
-                             SystemWebAppType::SHORTCUT_CUSTOMIZATION) {}
+                             SystemWebAppType::SHORTCUT_CUSTOMIZATION,
+                             create_controller_func) {}
 };
 
 class ShortcutCustomizationAppUI : public ui::MojoWebUIController {
  public:
-  explicit ShortcutCustomizationAppUI(content::WebUI* web_ui);
+  ShortcutCustomizationAppUI(
+      content::WebUI* web_ui,
+      std::unique_ptr<shortcut_ui::ShortcutCustomizationDelegate>
+          shortcut_customization_delegate);
   ShortcutCustomizationAppUI(const ShortcutCustomizationAppUI&) = delete;
   ShortcutCustomizationAppUI& operator=(const ShortcutCustomizationAppUI&) =
       delete;
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index f451090..51442fe 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -140,32 +140,6 @@
   }
 }
 
-// Returns true for windows that are interesting from an all-desk z-order
-// tracking perspective.
-bool IsZOrderTracked(aura::Window* window) {
-  return window->GetType() == aura::client::WindowType::WINDOW_TYPE_NORMAL &&
-         window->GetProperty(aura::client::kZOrderingKey) ==
-             ui::ZOrderLevel::kNormal;
-}
-
-// Get the position of `window` in `windows` (as filtered by `IsZOrderTracked`)
-// in reverse order. If `window` is not in the list (or isn't z-order tracked),
-// then nullopt is returned.
-absl::optional<size_t> GetWindowZOrder(
-    const std::vector<aura::Window*>& windows,
-    aura::Window* window) {
-  size_t position = 0;
-  for (aura::Window* w : base::Reversed(windows)) {
-    if (IsZOrderTracked(w)) {
-      if (w == window)
-        return position;
-      ++position;
-    }
-  }
-
-  return absl::nullopt;
-}
-
 // Used to temporarily turn off the automatic window positioning while windows
 // are being moved between desks.
 class ScopedWindowPositionerDisabler {
@@ -367,7 +341,8 @@
 
     // Find z-order of the added window.
     auto* container = GetDeskContainerForRoot(root);
-    if (auto order = GetWindowZOrder(container->children(), window)) {
+    if (auto order =
+            desks_util::GetWindowZOrder(container->children(), window)) {
       for (auto& adw : adw_data) {
         // All desk windows that are below the added window will have their
         // order updated (since they are now farther from the top).
@@ -427,7 +402,7 @@
     return;
 
   aura::Window* container = GetDeskContainerForRoot(root);
-  if (auto order = GetWindowZOrder(container->children(), window)) {
+  if (auto order = desks_util::GetWindowZOrder(container->children(), window)) {
     for (auto& info : adw_data) {
       // All-desk windows that are below the removed window will have their
       // order updated (since they are now closer to the top).
@@ -831,7 +806,7 @@
     adw_data.clear();
     size_t order = 0;
     for (aura::Window* window : base::Reversed(desk_windows)) {
-      if (IsZOrderTracked(window)) {
+      if (desks_util::IsZOrderTracked(window)) {
         if (desks_util::IsWindowVisibleOnAllWorkspaces(window))
           adw_data.push_back({.window = window, .order = order});
         ++order;
diff --git a/ash/wm/desks/desk.h b/ash/wm/desks/desk.h
index 5810f70..7e64d975 100644
--- a/ash/wm/desks/desk.h
+++ b/ash/wm/desks/desk.h
@@ -50,6 +50,18 @@
     virtual void OnDeskNameChanged(const std::u16string& new_name) = 0;
   };
 
+  // Tracks stacking order for a window that is visible on all desks. This is
+  // used to support per-desk z-orders for all-desk windows. Entries are stored
+  // in ascending `order`.
+  struct AllDeskWindowStackingData {
+    aura::Window* window = nullptr;
+    // The z-order of the window.
+    // Note: this is reversed from how child windows are ordered in
+    // `aura::Window`, so an entry with `order == 0` means topmost.
+    // Note: this order ignores non-normal windows.
+    size_t order = 0;
+  };
+
   explicit Desk(int associated_container_id, bool desk_being_restored = false);
 
   Desk(const Desk&) = delete;
@@ -101,6 +113,11 @@
     interacted_with_this_week_ = interacted_with_this_week;
   }
 
+  const base::flat_map<aura::Window*, std::vector<AllDeskWindowStackingData>>&
+  all_desk_window_stacking() const {
+    return all_desk_window_stacking_;
+  }
+
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
@@ -290,18 +307,6 @@
   int first_day_visited_ = -1;
   int last_day_visited_ = -1;
 
-  // Tracks stacking order for a window that is visible on all desks. This is
-  // used to support per-desk z-orders for all-desk windows. Entries are stored
-  // in ascending `order`.
-  struct AllDeskWindowStackingData {
-    aura::Window* window = nullptr;
-    // The z-order of the window.
-    // Note: this is reversed from how child windows are ordered in
-    // `aura::Window`, so an entry with `order == 0` means topmost.
-    // Note: this order ignores non-normal windows.
-    size_t order = 0;
-  };
-
   // Stacking data for all all-desk windows. Ordered from topmost and
   // down. Keyed by root window.
   base::flat_map<aura::Window*, std::vector<AllDeskWindowStackingData>>
diff --git a/ash/wm/desks/desk_preview_view.cc b/ash/wm/desks/desk_preview_view.cc
index f4617f2..fa781d5 100644
--- a/ash/wm/desks/desk_preview_view.cc
+++ b/ash/wm/desks/desk_preview_view.cc
@@ -117,16 +117,37 @@
   return iter == layers_data.end() ? LayerData{} : iter->second;
 }
 
+// Get the z-order of all-desk `window` in `desk` for `root`. If it does not
+// exist, then nullopt is returned. Please note, the z-order information is
+// retrieved from the stored stacking data of `desk` for all-desk windows.
+absl::optional<size_t> GetWindowZOrderForDeskAndRoot(const aura::Window* window,
+                                                     const Desk* desk,
+                                                     const aura::Window* root) {
+  const auto& adw_by_root = desk->all_desk_window_stacking();
+
+  if (auto it = adw_by_root.find(root); it != adw_by_root.end()) {
+    for (auto& adw : it->second) {
+      if (adw.window == window)
+        return adw.order;
+    }
+  }
+
+  return absl::nullopt;
+}
+
 // Appends clones of all the visible on all desks windows' layers to
-// |out_desk_container_children|. Should only be called if
-// |visible_on_all_desks_windows| is not empty.
+// `out_desk_container_children`. Should only be called if
+// `visible_on_all_desks_windows` is not empty.
 void AppendVisibleOnAllDesksWindowsToDeskLayer(
     const base::flat_set<aura::Window*>& visible_on_all_desks_windows,
     const base::flat_map<ui::Layer*, LayerData>& layers_data,
-    std::vector<ui::Layer*>* out_desk_container_children) {
+    std::vector<ui::Layer*>* out_desk_container_children,
+    aura::Window* desk_container) {
   DCHECK(!visible_on_all_desks_windows.empty());
   auto mru_windows =
       Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
+  const Desk* desk = desks_util::GetDeskForContext(desk_container);
+  aura::Window* root = desk_container->GetRootWindow();
 
   for (auto* window : visible_on_all_desks_windows) {
     const LayerData layer_data =
@@ -138,36 +159,53 @@
     if (window_iter == mru_windows.end())
       continue;
 
-    auto closest_window_below_iter = std::next(window_iter);
-    while (closest_window_below_iter != mru_windows.end() &&
-           !base::Contains(*out_desk_container_children,
-                           (*closest_window_below_iter)->layer())) {
-      // Find the closest window to |window| in the MRU tracker whose layer also
-      // is in |out_desk_container_children|. This window will be used to
-      // determine the stacking order of the visible on all desks window in the
-      // preview view.
-      closest_window_below_iter = std::next(closest_window_below_iter);
+    auto insertion_point_iter = out_desk_container_children->end();
+    auto desk_windows = desk_container->children();
+
+    // Find z order of `window`. If `features::IsPerDeskZOrderEnabled()` is not
+    // on, default value of zero will be used so `window` would be put on top.
+    size_t window_order =
+        GetWindowZOrderForDeskAndRoot(window, desk, root).value_or(0);
+
+    // If `desk` has no child window, or `window` has lowest z order, use
+    // default `insertion_point_iter` to put it on top.
+    if (!desk_windows.empty() && window_order) {
+      // Find the nearest window that should be on top of `window`.
+      size_t order = 0, target_idx = desk_windows.size();
+      for (int i = desk_windows.size() - 1; i >= 0 && order < window_order;
+           i--) {
+        if (desks_util::IsZOrderTracked(window)) {
+          target_idx = static_cast<size_t>(i);
+          ++order;
+        }
+      }
+
+      // Move to the next nearest window until its layer is in the
+      // `out_desk_container_children`.
+      for (size_t i = target_idx; i < desk_windows.size(); i++) {
+        if (base::Contains(*out_desk_container_children,
+                           desk_windows[i]->layer())) {
+          insertion_point_iter = base::ranges::find(
+              *out_desk_container_children, desk_windows[i]->layer());
+          break;
+        }
+      }
     }
 
-    auto insertion_point_iter =
-        closest_window_below_iter == mru_windows.end()
-            ? out_desk_container_children->begin()
-            : std::next(
-                  base::ranges::find(*out_desk_container_children,
-                                     (*closest_window_below_iter)->layer()));
     out_desk_container_children->insert(insertion_point_iter, window->layer());
   }
 }
 
-// Recursively mirrors |source_layer| and its children and adds them as children
-// of |parent|, taking into account the given |layers_data|. If the layer data
-// of |source_layer| has |should_clear_transform| set to true, the transforms of
+// Recursively mirrors `source_layer` and its children and adds them as children
+// of `parent`, taking into account the given |layers_data|. If the layer data
+// of `source_layer` has `should_clear_transform` set to true, the transforms of
 // its mirror layers will be reset to identity.
-void MirrorLayerTree(ui::Layer* source_layer,
-                     ui::Layer* parent,
-                     const base::flat_map<ui::Layer*, LayerData>& layers_data,
-                     const base::flat_set<aura::Window*>&
-                         visible_on_all_desks_windows_to_mirror) {
+void MirrorLayerTree(
+    ui::Layer* source_layer,
+    ui::Layer* parent,
+    const base::flat_map<ui::Layer*, LayerData>& layers_data,
+    const base::flat_set<aura::Window*>& visible_on_all_desks_windows_to_mirror,
+    aura::Window* desk_container) {
   const LayerData layer_data = GetLayerDataEntry(layers_data, source_layer);
   if (layer_data.should_skip_layer)
     return;
@@ -181,13 +219,14 @@
     // preview so for inactive desks, we need to append the layers of visible on
     // all desks windows.
     AppendVisibleOnAllDesksWindowsToDeskLayer(
-        visible_on_all_desks_windows_to_mirror, layers_data, &children);
+        visible_on_all_desks_windows_to_mirror, layers_data, &children,
+        desk_container);
   }
   for (auto* child : children) {
     // Visible on all desks windows only needed to be added to the subtree once
     // so use an empty set for subsequent calls.
-    MirrorLayerTree(child, mirror, layers_data,
-                    base::flat_set<aura::Window*>());
+    MirrorLayerTree(child, mirror, layers_data, base::flat_set<aura::Window*>(),
+                    desk_container);
   }
 
   mirror->set_sync_bounds_with_source(true);
@@ -378,14 +417,16 @@
 
   auto* desk_container_layer = desk_container->layer();
   MirrorLayerTree(desk_container_layer, mirrored_content_root_layer.get(),
-                  layers_data, visible_on_all_desks_windows_to_mirror);
+                  layers_data, visible_on_all_desks_windows_to_mirror,
+                  desk_container);
 
   // Since floated window is not stored in desk container, we need to mirror it
   // separately.
   if (floated_window) {
     auto* floated_window_layer = floated_window->layer();
     MirrorLayerTree(floated_window_layer, mirrored_content_root_layer.get(),
-                    layers_data, /*visible_on_all_desks_windows_to_mirror=*/{});
+                    layers_data, /*visible_on_all_desks_windows_to_mirror=*/{},
+                    desk_container);
   }
 
   // Add the root of the mirrored layer tree as a child of the
diff --git a/ash/wm/desks/desks_util.cc b/ash/wm/desks/desks_util.cc
index b6c3e991..deb7c71 100644
--- a/ash/wm/desks/desks_util.cc
+++ b/ash/wm/desks/desks_util.cc
@@ -18,6 +18,7 @@
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "base/containers/adapters.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
@@ -194,6 +195,27 @@
          aura::client::kWindowWorkspaceVisibleOnAllWorkspaces;
 }
 
+bool IsZOrderTracked(aura::Window* window) {
+  return window->GetType() == aura::client::WindowType::WINDOW_TYPE_NORMAL &&
+         window->GetProperty(aura::client::kZOrderingKey) ==
+             ui::ZOrderLevel::kNormal;
+}
+
+absl::optional<size_t> GetWindowZOrder(
+    const std::vector<aura::Window*>& windows,
+    aura::Window* window) {
+  size_t position = 0;
+  for (aura::Window* w : base::Reversed(windows)) {
+    if (IsZOrderTracked(w)) {
+      if (w == window)
+        return position;
+      ++position;
+    }
+  }
+
+  return absl::nullopt;
+}
+
 }  // namespace desks_util
 
 }  // namespace ash
diff --git a/ash/wm/desks/desks_util.h b/ash/wm/desks/desks_util.h
index 03aafa5..49157722 100644
--- a/ash/wm/desks/desks_util.h
+++ b/ash/wm/desks/desks_util.h
@@ -80,6 +80,17 @@
 // Returns whether a |window| is visible on all workspaces.
 ASH_EXPORT bool IsWindowVisibleOnAllWorkspaces(const aura::Window* window);
 
+// Returns true for windows that are interesting from an all-desk z-order
+// tracking perspective.
+ASH_EXPORT bool IsZOrderTracked(aura::Window* window);
+
+// Get the position of `window` in `windows` (as filtered by `IsZOrderTracked`)
+// in reverse order. If `window` is not in the list (or isn't z-order tracked),
+// then nullopt is returned.
+ASH_EXPORT absl::optional<size_t> GetWindowZOrder(
+    const std::vector<aura::Window*>& windows,
+    aura::Window* window);
+
 // Move an item at |old_index| to |new_index|.
 template <typename T>
 ASH_EXPORT void ReorderItem(std::vector<T>& items,
diff --git a/base/BUILD.gn b/base/BUILD.gn
index c452179..1ad64d9 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2381,8 +2381,6 @@
       "trace_event/builtin_categories.h",
       "trace_event/category_registry.cc",
       "trace_event/category_registry.h",
-      "trace_event/event_name_filter.cc",
-      "trace_event/event_name_filter.h",
       "trace_event/heap_profiler.h",
       "trace_event/interned_args_helper.cc",
       "trace_event/interned_args_helper.h",
@@ -2423,8 +2421,6 @@
       "trace_event/trace_config_category_filter.h",
       "trace_event/trace_conversion_helper.h",
       "trace_event/trace_event.h",
-      "trace_event/trace_event_filter.cc",
-      "trace_event/trace_event_filter.h",
       "trace_event/trace_event_impl.cc",
       "trace_event/trace_event_impl.h",
       "trace_event/trace_event_memory_overhead.cc",
@@ -3951,7 +3947,6 @@
   if (enable_base_tracing) {
     sources += [
       "test/trace_event_analyzer_unittest.cc",
-      "trace_event/event_name_filter_unittest.cc",
       "trace_event/heap_profiler_allocation_context_tracker_unittest.cc",
       "trace_event/memory_allocator_dump_unittest.cc",
       "trace_event/memory_dump_manager_unittest.cc",
@@ -3963,8 +3958,6 @@
       "trace_event/trace_category_unittest.cc",
       "trace_event/trace_config_unittest.cc",
       "trace_event/trace_conversion_helper_unittest.cc",
-      "trace_event/trace_event_filter_test_utils.cc",
-      "trace_event/trace_event_filter_test_utils.h",
       "trace_event/trace_event_unittest.cc",
       "trace_event/traced_value_support_unittest.cc",
       "trace_event/traced_value_unittest.cc",
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index 3477c38b..312db52 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -13,7 +13,6 @@
 import android.system.Os;
 
 import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.BaseSwitches;
@@ -29,7 +28,6 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.compat.ApiHelperForM;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.UmaRecorderHolder;
 import org.chromium.build.BuildConfig;
@@ -38,7 +36,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Locale;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -417,16 +414,6 @@
         return mMediator;
     }
 
-    /**
-     * Call this method to determine if the chromium project must load the library
-     * directly from a zip file.
-     */
-    private static boolean isInZipFile() {
-        // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true
-        // iff the library remains embedded in the APK zip file on the target.
-        return NativeLibraries.sUseLibraryInZipFile;
-    }
-
     public static LibraryLoader getInstance() {
         return sInstance;
     }
@@ -507,26 +494,13 @@
                 useChromiumLinker(), mUseModernLinker);
     }
 
-    // LegacyLinker is buggy on Android 10, causing crashes (see crbug.com/980304).
-    //
-    // Rather than preventing people from running chrome_public_apk on Android 10, fallback to the
-    // system linker on this platform. We lose relocation sharing as a side-effect, but this
-    // configuration does not ship to users (since we only use LegacyLinker for APKs targeted at
-    // pre-N users).
-    //
-    // Note: This cannot be done in the build configuration, as otherwise chrome_public_apk cannot
-    // both be used as the basis to ship on L, and the default APK used by developers on 10+.
-    private boolean forceSystemLinker() {
-        return mUseChromiumLinker && !mUseModernLinker
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
-    }
-
     // Whether a Linker subclass is used for loading. Even if returns |true|, the Linker can
     // fall back to using the system dynamic linker on failure. Also it is common for App Zygote to
     // choose loading with the system linker when sharing RELRO with the browser process is not
     // supported.
+    // TODO(crbug.com/1383210): Remove the LegacyLinker and fold the ModernLinker into Linker.
     private boolean useChromiumLinker() {
-        return mUseChromiumLinker && !forceSystemLinker();
+        return mUseChromiumLinker && mUseModernLinker;
     }
 
     /**
@@ -803,50 +777,20 @@
 
     private void loadWithChromiumLinker(ApplicationInfo appInfo, String library) {
         Linker linker = getLinker();
-
-        if (isInZipFile()) {
-            String sourceDir = appInfo.sourceDir;
-            linker.setApkFilePath(sourceDir);
-            Log.i(TAG, "Loading %s from within %s", library, sourceDir);
-        } else {
-            Log.i(TAG, "Loading %s", library);
-        }
-
+        String sourceDir = appInfo.sourceDir;
+        linker.setApkFilePath(sourceDir);
+        Log.i(TAG, "Loading %s from within %s", library, sourceDir);
         linker.loadLibrary(library); // May throw UnsatisfiedLinkError.
         getMediator().recordLinkerHistogramsAfterLibraryLoad();
     }
 
     @GuardedBy("mLock")
-    @SuppressLint({"UnsafeDynamicallyLoadedCode", "ObsoleteSdkInt"})
+    @SuppressLint({"UnsafeDynamicallyLoadedCode"})
     private void loadWithSystemLinkerAlreadyLocked(ApplicationInfo appInfo, boolean inZygote) {
         setEnvForNative();
         preloadAlreadyLocked(appInfo.packageName, inZygote);
-
-        // If the libraries are located in the zip file, assert that the device API level is M or
-        // higher. On devices <=M, the libraries should always be loaded by LegacyLinker.
-        assert !isInZipFile() || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
-
-        // Load libraries using the system linker.
         for (String library : NativeLibraries.LIBRARIES) {
-            // TODO(crbug.com/1337134): Always use System.loadLibrary().
-            boolean isTrichrome = !forceSystemLinker() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
-            if (!isInZipFile() || isTrichrome) {
-                System.loadLibrary(library);
-            } else {
-                // Load directly from the APK.
-                boolean is64Bit = ApiHelperForM.isProcess64Bit();
-                String zipFilePath = appInfo.sourceDir;
-                boolean crazyPrefix = forceSystemLinker(); // See comment in this function.
-                String fullPath = zipFilePath + "!/"
-                        + makeLibraryPathInZipFile(library, crazyPrefix, is64Bit);
-                Log.i(TAG, "libraryName: %s", fullPath);
-                if (crazyPrefix) {
-                    Log.w(TAG,
-                            "Forcing system linker, relocations will not be shared. "
-                                    + "This negatively impacts memory usage.");
-                }
-                System.load(fullPath);
-            }
+            System.loadLibrary(library);
         }
     }
 
@@ -919,46 +863,6 @@
         }
     }
 
-    /**
-     * @param library The library name that is looking for.
-     * @param crazyPrefix true iff adding crazy linker prefix to the file name.
-     * @param is64Bit true if the caller think it's run on a 64 bit device.
-     * @return the library path name in the zip file.
-     */
-    @NonNull
-    public static String makeLibraryPathInZipFile(
-            String library, boolean crazyPrefix, boolean is64Bit) {
-        // Determine the ABI string that Android uses to find native libraries. Values are described
-        // in: https://developer.android.com/ndk/guides/abis.html
-        // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet
-        // and Cast load the native library via other paths.
-        String cpuAbi;
-        switch (NativeLibraries.sCpuFamily) {
-            case NativeLibraries.CPU_FAMILY_ARM:
-                cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a";
-                break;
-            case NativeLibraries.CPU_FAMILY_X86:
-                cpuAbi = is64Bit ? "x86_64" : "x86";
-                break;
-            case NativeLibraries.CPU_FAMILY_MIPS:
-                cpuAbi = is64Bit ? "mips64" : "mips";
-                break;
-            default:
-                throw new RuntimeException("Unknown CPU ABI for native libraries");
-        }
-
-        // When both the Chromium linker and zip-uncompressed native libraries are used,
-        // the build system renames the native shared libraries with a 'crazy.' prefix
-        // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so").
-        //
-        // This prevents the package manager from extracting them at installation/update time
-        // to the /data directory. The libraries can still be accessed directly by the Chromium
-        // linker from the APK.
-        String crazyPart = crazyPrefix ? "crazy." : "";
-        return String.format(
-                Locale.US, "lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library));
-    }
-
     // The WebView requires the Command Line to be switched over before
     // initialization is done. This is okay in the WebView's case since the
     // JNI is already loaded by this point.
diff --git a/base/trace_event/event_name_filter.cc b/base/trace_event/event_name_filter.cc
deleted file mode 100644
index 1078cc4a..0000000
--- a/base/trace_event/event_name_filter.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/trace_event/event_name_filter.h"
-
-#include "base/trace_event/trace_event_impl.h"
-
-namespace base {
-namespace trace_event {
-
-// static
-const char EventNameFilter::kName[] = "event_whitelist_predicate";
-
-EventNameFilter::EventNameFilter(
-    std::unique_ptr<EventNamesAllowlist> event_names_allowlist)
-    : event_names_allowlist_(std::move(event_names_allowlist)) {}
-
-EventNameFilter::~EventNameFilter() = default;
-
-bool EventNameFilter::FilterTraceEvent(const TraceEvent& trace_event) const {
-  return event_names_allowlist_->count(trace_event.name()) != 0;
-}
-
-}  // namespace trace_event
-}  // namespace base
diff --git a/base/trace_event/event_name_filter.h b/base/trace_event/event_name_filter.h
deleted file mode 100644
index fd9723c..0000000
--- a/base/trace_event/event_name_filter.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
-#define BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
-
-#include <memory>
-#include <string>
-#include <unordered_set>
-
-#include "base/base_export.h"
-#include "base/trace_event/trace_event_filter.h"
-
-namespace base {
-namespace trace_event {
-
-class TraceEvent;
-
-// Filters trace events by checking the full name against an allowlist.
-// The current implementation is quite simple and dumb and just uses a
-// hashtable which requires char* to std::string conversion. It could be smarter
-// and use a bloom filter trie. However, today this is used too rarely to
-// justify that cost.
-class BASE_EXPORT EventNameFilter : public TraceEventFilter {
- public:
-  using EventNamesAllowlist = std::unordered_set<std::string>;
-  static const char kName[];
-
-  EventNameFilter(std::unique_ptr<EventNamesAllowlist>);
-
-  EventNameFilter(const EventNameFilter&) = delete;
-  EventNameFilter& operator=(const EventNameFilter&) = delete;
-
-  ~EventNameFilter() override;
-
-  // TraceEventFilter implementation.
-  bool FilterTraceEvent(const TraceEvent&) const override;
-
- private:
-  std::unique_ptr<const EventNamesAllowlist> event_names_allowlist_;
-};
-
-}  // namespace trace_event
-}  // namespace base
-
-#endif  // BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
diff --git a/base/trace_event/event_name_filter_unittest.cc b/base/trace_event/event_name_filter_unittest.cc
deleted file mode 100644
index 3595a24..0000000
--- a/base/trace_event/event_name_filter_unittest.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/trace_event/event_name_filter.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/trace_event/trace_event_impl.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-namespace trace_event {
-
-const TraceEvent& MakeTraceEvent(const char* name) {
-  static TraceEvent event;
-  event.Reset(0, TimeTicks(), ThreadTicks(), 'b', nullptr, name, "", 0, 0,
-              nullptr, 0);
-  return event;
-}
-
-TEST(TraceEventNameFilterTest, Allowlist) {
-  auto empty_allowlist =
-      std::make_unique<EventNameFilter::EventNamesAllowlist>();
-  auto filter = std::make_unique<EventNameFilter>(std::move(empty_allowlist));
-
-  // No events should be filtered if the allowlist is empty.
-  EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foo")));
-
-  auto allowlist = std::make_unique<EventNameFilter::EventNamesAllowlist>();
-  allowlist->insert("foo");
-  allowlist->insert("bar");
-  filter = std::make_unique<EventNameFilter>(std::move(allowlist));
-  EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("foo")));
-  EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("fooz")));
-  EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("afoo")));
-  EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("bar")));
-  EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foobar")));
-}
-
-}  // namespace trace_event
-}  // namespace base
diff --git a/base/trace_event/trace_event_filter.cc b/base/trace_event/trace_event_filter.cc
deleted file mode 100644
index c329b2e..0000000
--- a/base/trace_event/trace_event_filter.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/trace_event/trace_event_filter.h"
-
-namespace base {
-namespace trace_event {
-
-TraceEventFilter::TraceEventFilter() = default;
-TraceEventFilter::~TraceEventFilter() = default;
-
-void TraceEventFilter::EndEvent(const char* category_name,
-                                const char* event_name) const {}
-
-}  // namespace trace_event
-}  // namespace base
diff --git a/base/trace_event/trace_event_filter.h b/base/trace_event/trace_event_filter.h
deleted file mode 100644
index 79e576f..0000000
--- a/base/trace_event/trace_event_filter.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
-#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
-
-#include "base/base_export.h"
-
-namespace base {
-namespace trace_event {
-
-class TraceEvent;
-
-// TraceEventFilter is like iptables for TRACE_EVENT macros. Filters can be
-// enabled on a per-category basis, hence a single filter instance can serve
-// more than a TraceCategory. There are two use cases for filters:
-// 1. Snooping TRACE_EVENT macros without adding them to the TraceLog. This is
-//    possible by setting the ENABLED_FOR_FILTERING flag on a category w/o
-//    ENABLED_FOR_RECORDING (see TraceConfig for user-facing configuration).
-// 2. Filtering TRACE_EVENT macros before they are added to the TraceLog. This
-//    requires both the ENABLED_FOR_FILTERING and ENABLED_FOR_RECORDING flags
-//    on the category.
-// More importantly, filters must be thread-safe. The FilterTraceEvent and
-// EndEvent methods can be called concurrently as trace macros are hit on
-// different threads.
-class BASE_EXPORT TraceEventFilter {
- public:
-  TraceEventFilter();
-
-  TraceEventFilter(const TraceEventFilter&) = delete;
-  TraceEventFilter& operator=(const TraceEventFilter&) = delete;
-
-  virtual ~TraceEventFilter();
-
-  // If the category is ENABLED_FOR_RECORDING, the event is added iff all the
-  // filters enabled for the category return true. false causes the event to be
-  // discarded.
-  virtual bool FilterTraceEvent(const TraceEvent& trace_event) const = 0;
-
-  // Notifies the end of a duration event when the RAII macro goes out of scope.
-  virtual void EndEvent(const char* category_name,
-                        const char* event_name) const;
-};
-
-}  // namespace trace_event
-}  // namespace base
-
-#endif  // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
diff --git a/base/trace_event/trace_event_filter_test_utils.cc b/base/trace_event/trace_event_filter_test_utils.cc
deleted file mode 100644
index e3a3da0..0000000
--- a/base/trace_event/trace_event_filter_test_utils.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/trace_event/trace_event_filter_test_utils.h"
-
-#include <memory>
-
-#include "base/check.h"
-
-namespace base {
-namespace trace_event {
-
-namespace {
-TestEventFilter::HitsCounter* g_hits_counter;
-}  // namespace;
-
-// static
-const char TestEventFilter::kName[] = "testing_predicate";
-bool TestEventFilter::filter_return_value_;
-
-// static
-std::unique_ptr<TraceEventFilter> TestEventFilter::Factory(
-    const std::string& predicate_name) {
-  std::unique_ptr<TraceEventFilter> res;
-  if (predicate_name == kName)
-    res = std::make_unique<TestEventFilter>();
-  return res;
-}
-
-TestEventFilter::TestEventFilter() = default;
-TestEventFilter::~TestEventFilter() = default;
-
-bool TestEventFilter::FilterTraceEvent(const TraceEvent& trace_event) const {
-  if (g_hits_counter)
-    g_hits_counter->filter_trace_event_hit_count++;
-  return filter_return_value_;
-}
-
-void TestEventFilter::EndEvent(const char* category_name,
-                               const char* name) const {
-  if (g_hits_counter)
-    g_hits_counter->end_event_hit_count++;
-}
-
-TestEventFilter::HitsCounter::HitsCounter() {
-  Reset();
-  DCHECK(!g_hits_counter);
-  g_hits_counter = this;
-}
-
-TestEventFilter::HitsCounter::~HitsCounter() {
-  DCHECK(g_hits_counter);
-  g_hits_counter = nullptr;
-}
-
-void TestEventFilter::HitsCounter::Reset() {
-  filter_trace_event_hit_count = 0;
-  end_event_hit_count = 0;
-}
-
-}  // namespace trace_event
-}  // namespace base
diff --git a/base/trace_event/trace_event_filter_test_utils.h b/base/trace_event/trace_event_filter_test_utils.h
deleted file mode 100644
index 26f9c2a..0000000
--- a/base/trace_event/trace_event_filter_test_utils.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
-#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
-
-#include <memory>
-#include <string>
-
-#include "base/trace_event/trace_event_filter.h"
-
-namespace base {
-namespace trace_event {
-
-class TestEventFilter : public TraceEventFilter {
- public:
-  struct HitsCounter {
-    HitsCounter();
-    ~HitsCounter();
-    void Reset();
-    size_t filter_trace_event_hit_count;
-    size_t end_event_hit_count;
-  };
-
-  static const char kName[];
-
-  // Factory method for TraceLog::SetFilterFactoryForTesting().
-  static std::unique_ptr<TraceEventFilter> Factory(
-      const std::string& predicate_name);
-
-  TestEventFilter();
-  TestEventFilter(const TestEventFilter&) = delete;
-  TestEventFilter& operator=(const TestEventFilter&) = delete;
-  ~TestEventFilter() override;
-
-  // TraceEventFilter implementation.
-  bool FilterTraceEvent(const TraceEvent& trace_event) const override;
-  void EndEvent(const char* category_name, const char* name) const override;
-
-  static void set_filter_return_value(bool value) {
-    filter_return_value_ = value;
-  }
-
- private:
-  static bool filter_return_value_;
-};
-
-}  // namespace trace_event
-}  // namespace base
-
-#endif  // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc
index 35d3a49..e27ae69 100644
--- a/base/trace_event/trace_event_unittest.cc
+++ b/base/trace_event/trace_event_unittest.cc
@@ -37,10 +37,7 @@
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread.h"
 #include "base/time/time.h"
-#include "base/trace_event/event_name_filter.h"
 #include "base/trace_event/trace_buffer.h"
-#include "base/trace_event/trace_event_filter.h"
-#include "base/trace_event/trace_event_filter_test_utils.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -153,8 +150,7 @@
   }
 
   void EndTraceAndFlushAsync(WaitableEvent* flush_complete_event) {
-    TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE |
-                                         TraceLog::FILTERING_MODE);
+    TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE);
     TraceLog::GetInstance()->Flush(base::BindRepeating(
         &TraceEventTestFixture::OnTraceDataCollected,
         base::Unretained(static_cast<TraceEventTestFixture*>(this)),
@@ -2468,186 +2464,6 @@
 }
 #endif  // !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 
-// Runtime filtering isn't supported with Perfetto.
-#if !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
-TEST_F(TraceEventTestFixture, TraceFilteringMode) {
-  const char config_json[] =
-      "{"
-      "  \"event_filters\": ["
-      "     {"
-      "       \"filter_predicate\": \"testing_predicate\", "
-      "       \"included_categories\": [\"*\"]"
-      "     }"
-      "  ]"
-      "}";
-
-  // Run RECORDING_MODE within FILTERING_MODE:
-  TestEventFilter::HitsCounter filter_hits_counter;
-  TestEventFilter::set_filter_return_value(true);
-  TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory);
-
-  // Only filtering mode is enabled with test filters.
-  TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json),
-                                      TraceLog::FILTERING_MODE);
-  EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes());
-  {
-    void* ptr = this;
-    TRACE_EVENT0("test_c0", "name0");
-    TRACE_EVENT_ASYNC_BEGIN0("test_c1", "name1", ptr);
-    TRACE_EVENT_INSTANT0("test_c0", "name0", TRACE_EVENT_SCOPE_THREAD);
-    TRACE_EVENT_ASYNC_END0("test_c1", "name1", ptr);
-  }
-
-  // Recording mode is enabled when filtering mode is turned on.
-  TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""),
-                                      TraceLog::RECORDING_MODE);
-  EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE,
-            TraceLog::GetInstance()->enabled_modes());
-  { TRACE_EVENT0("test_c2", "name2"); }
-  // Only recording mode is disabled and filtering mode will continue to run.
-  TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE);
-  EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes());
-
-  { TRACE_EVENT0("test_c0", "name0"); }
-  // Filtering mode is disabled and no tracing mode should be enabled.
-  TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE);
-  EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes());
-
-  EndTraceAndFlush();
-  EXPECT_FALSE(FindMatchingValue("cat", "test_c0"));
-  EXPECT_FALSE(FindMatchingValue("cat", "test_c1"));
-  EXPECT_FALSE(FindMatchingValue("name", "name0"));
-  EXPECT_FALSE(FindMatchingValue("name", "name1"));
-  EXPECT_TRUE(FindMatchingValue("cat", "test_c2"));
-  EXPECT_TRUE(FindMatchingValue("name", "name2"));
-  EXPECT_EQ(6u, filter_hits_counter.filter_trace_event_hit_count);
-  EXPECT_EQ(3u, filter_hits_counter.end_event_hit_count);
-  Clear();
-  filter_hits_counter.Reset();
-
-  // Run FILTERING_MODE within RECORDING_MODE:
-  // Only recording mode is enabled and all events must be recorded.
-  TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""),
-                                      TraceLog::RECORDING_MODE);
-  EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes());
-  { TRACE_EVENT0("test_c0", "name0"); }
-
-  // Filtering mode is also enabled and all events must be filtered-out.
-  TestEventFilter::set_filter_return_value(false);
-  TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json),
-                                      TraceLog::FILTERING_MODE);
-  EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE,
-            TraceLog::GetInstance()->enabled_modes());
-  { TRACE_EVENT0("test_c1", "name1"); }
-  // Only filtering mode is disabled and recording mode should continue to run
-  // with all events being recorded.
-  TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE);
-  EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes());
-
-  { TRACE_EVENT0("test_c2", "name2"); }
-  // Recording mode is disabled and no tracing mode should be enabled.
-  TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE);
-  EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes());
-
-  EndTraceAndFlush();
-  EXPECT_TRUE(FindMatchingValue("cat", "test_c0"));
-  EXPECT_TRUE(FindMatchingValue("cat", "test_c2"));
-  EXPECT_TRUE(FindMatchingValue("name", "name0"));
-  EXPECT_TRUE(FindMatchingValue("name", "name2"));
-  EXPECT_FALSE(FindMatchingValue("cat", "test_c1"));
-  EXPECT_FALSE(FindMatchingValue("name", "name1"));
-  EXPECT_EQ(1u, filter_hits_counter.filter_trace_event_hit_count);
-  EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count);
-  Clear();
-}
-
-TEST_F(TraceEventTestFixture, EventFiltering) {
-  const char config_json[] =
-      "{"
-      "  \"included_categories\": ["
-      "    \"test_filtered_cat\","
-      "    \"test_unfiltered_cat\","
-      "    \"" TRACE_DISABLED_BY_DEFAULT("test_filtered_cat") "\","
-      "    \"" TRACE_DISABLED_BY_DEFAULT("test_unfiltered_cat") "\"],"
-      "  \"event_filters\": ["
-      "     {"
-      "       \"filter_predicate\": \"testing_predicate\", "
-      "       \"included_categories\": ["
-      "         \"test_filtered_cat\","
-      "         \"" TRACE_DISABLED_BY_DEFAULT("test_filtered_cat") "\"]"
-      "     }"
-      "    "
-      "  ]"
-      "}";
-
-  TestEventFilter::HitsCounter filter_hits_counter;
-  TestEventFilter::set_filter_return_value(true);
-  TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory);
-
-  TraceConfig trace_config(config_json);
-  TraceLog::GetInstance()->SetEnabled(
-      trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE);
-  ASSERT_TRUE(TraceLog::GetInstance()->IsEnabled());
-
-  TRACE_EVENT0("test_filtered_cat", "a snake");
-  TRACE_EVENT0("test_filtered_cat", "a mushroom");
-  TRACE_EVENT0("test_unfiltered_cat", "a horse");
-
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("test_filtered_cat"), "a dog");
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("test_unfiltered_cat"), "a pony");
-
-  // This is scoped so we can test the end event being filtered.
-  { TRACE_EVENT0("test_filtered_cat", "another cat whoa"); }
-
-  EndTraceAndFlush();
-
-  EXPECT_EQ(4u, filter_hits_counter.filter_trace_event_hit_count);
-  EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count);
-}
-
-TEST_F(TraceEventTestFixture, EventAllowlistFiltering) {
-  std::string config_json = StringPrintf(
-      "{"
-      "  \"included_categories\": ["
-      "    \"test_filtered_cat\","
-      "    \"test_unfiltered_cat\","
-      "    \"" TRACE_DISABLED_BY_DEFAULT("test_filtered_cat") "\"],"
-      "  \"event_filters\": ["
-      "     {"
-      "       \"filter_predicate\": \"%s\", "
-      "       \"included_categories\": ["
-      "         \"test_filtered_cat\","
-      "         \"" TRACE_DISABLED_BY_DEFAULT("*") "\"], "
-      "       \"filter_args\": {"
-      "           \"event_name_allowlist\": [\"a snake\", \"a dog\"]"
-      "         }"
-      "     }"
-      "    "
-      "  ]"
-      "}",
-      EventNameFilter::kName);
-
-  TraceConfig trace_config(config_json);
-  TraceLog::GetInstance()->SetEnabled(
-      trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE);
-  EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
-
-  TRACE_EVENT0("test_filtered_cat", "a snake");
-  TRACE_EVENT0("test_filtered_cat", "a mushroom");
-  TRACE_EVENT0("test_unfiltered_cat", "a cat");
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("test_filtered_cat"), "a dog");
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("test_filtered_cat"), "a pony");
-
-  EndTraceAndFlush();
-
-  EXPECT_TRUE(FindMatchingValue("name", "a snake"));
-  EXPECT_FALSE(FindMatchingValue("name", "a mushroom"));
-  EXPECT_TRUE(FindMatchingValue("name", "a cat"));
-  EXPECT_TRUE(FindMatchingValue("name", "a dog"));
-  EXPECT_FALSE(FindMatchingValue("name", "a pony"));
-}
-#endif  // !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
-
 TEST_F(TraceEventTestFixture, ClockSyncEventsAreAlwaysAddedToTrace) {
   BeginSpecificTrace("-*");
   TRACE_EVENT_CLOCK_SYNC_RECEIVER(1);
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index 82b3c5dc..deb5197 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -39,7 +39,6 @@
 #include "base/threading/thread_id_name_manager.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "base/trace_event/event_name_filter.h"
 #include "base/trace_event/heap_profiler.h"
 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
 #include "base/trace_event/memory_dump_manager.h"
@@ -108,14 +107,6 @@
 
 TraceLog* g_trace_log_for_testing = nullptr;
 
-#define MAX_TRACE_EVENT_FILTERS 32
-
-// List of TraceEventFilter objects from the most recent tracing session.
-std::vector<std::unique_ptr<TraceEventFilter>>& GetCategoryGroupFilters() {
-  static auto* filters = new std::vector<std::unique_ptr<TraceEventFilter>>();
-  return *filters;
-}
-
 ThreadTicks ThreadNow() {
   return ThreadTicks::IsSupported()
              ? base::subtle::ThreadTicksNowIgnoringOverride()
@@ -174,18 +165,6 @@
   handle->event_index = static_cast<uint16_t>(event_index);
 }
 
-template <typename Function>
-void ForEachCategoryFilter(const unsigned char* category_group_enabled,
-                           Function filter_fn) {
-  const TraceCategory* category =
-      CategoryRegistry::GetCategoryByStatePtr(category_group_enabled);
-  uint32_t filter_bitmap = category->enabled_filters();
-  for (size_t index = 0; filter_bitmap != 0; filter_bitmap >>= 1, ++index) {
-    if (filter_bitmap & 1 && GetCategoryGroupFilters()[index])
-      filter_fn(GetCategoryGroupFilters()[index].get());
-  }
-}
-
 // The fallback arguments filtering function will filter away every argument.
 bool DefaultIsTraceEventArgsAllowlisted(
     const char* category_group_name,
@@ -669,9 +648,7 @@
 }
 
 TraceLog::TraceLog(int generation)
-    : enabled_modes_(0),
-      num_traces_recorded_(0),
-      process_sort_index_(0),
+    : process_sort_index_(0),
       process_id_hash_(0),
       process_id_(base::kNullProcessId),
       trace_options_(kInternalRecordUntilFull),
@@ -793,16 +770,14 @@
   lock_.AssertAcquired();
   DCHECK(category->is_valid());
   unsigned char state_flags = 0;
-  if (enabled_modes_ & RECORDING_MODE &&
-      trace_config_.IsCategoryGroupEnabled(category->name())) {
+  if (enabled_ && trace_config_.IsCategoryGroupEnabled(category->name())) {
     state_flags |= TraceCategory::ENABLED_FOR_RECORDING;
   }
 
   // TODO(primiano): this is a temporary workaround for catapult:#2341,
   // to guarantee that metadata events are always added even if the category
   // filter is "-*". See crbug.com/618054 for more details and long-term fix.
-  if (enabled_modes_ & RECORDING_MODE &&
-      category == CategoryRegistry::kCategoryMetadata) {
+  if (enabled_ && category == CategoryRegistry::kCategoryMetadata) {
     state_flags |= TraceCategory::ENABLED_FOR_RECORDING;
   }
 
@@ -813,64 +788,20 @@
   }
 #endif
 
-  uint32_t enabled_filters_bitmap = 0;
-  size_t index = 0;
-  for (const auto& event_filter : enabled_event_filters_) {
-    if (event_filter.IsCategoryGroupEnabled(category->name())) {
-      state_flags |= TraceCategory::ENABLED_FOR_FILTERING;
-      DCHECK(GetCategoryGroupFilters()[index]);
-      enabled_filters_bitmap |= 1 << index;
-    }
-    if (index++ >= MAX_TRACE_EVENT_FILTERS) {
-      NOTREACHED();
-      break;
-    }
-  }
-  category->set_enabled_filters(enabled_filters_bitmap);
   category->set_state(state_flags);
 }
 
 void TraceLog::UpdateCategoryRegistry() {
   lock_.AssertAcquired();
-  CreateFiltersForTraceConfig();
   for (TraceCategory& category : CategoryRegistry::GetAllCategories()) {
     UpdateCategoryState(&category);
   }
 }
 
-void TraceLog::CreateFiltersForTraceConfig() {
-  if (!(enabled_modes_ & FILTERING_MODE))
-    return;
-
-  // Filters were already added and tracing could be enabled. Filters list
-  // cannot be changed when trace events are using them.
-  if (GetCategoryGroupFilters().size())
-    return;
-
-  for (auto& filter_config : enabled_event_filters_) {
-    if (GetCategoryGroupFilters().size() >= MAX_TRACE_EVENT_FILTERS) {
-      NOTREACHED()
-          << "Too many trace event filters installed in the current session";
-      break;
-    }
-
-    std::unique_ptr<TraceEventFilter> new_filter;
-    const std::string& predicate_name = filter_config.predicate_name();
-    if (predicate_name == EventNameFilter::kName) {
-      auto allowlist = std::make_unique<std::unordered_set<std::string>>();
-      CHECK(filter_config.GetArgAsSet("event_name_allowlist", &*allowlist));
-      new_filter = std::make_unique<EventNameFilter>(std::move(allowlist));
-    } else {
-      if (filter_factory_for_testing_)
-        new_filter = filter_factory_for_testing_(predicate_name);
-      CHECK(new_filter) << "Unknown trace filter " << predicate_name;
-    }
-    GetCategoryGroupFilters().push_back(std::move(new_filter));
-  }
-}
-
 void TraceLog::SetEnabled(const TraceConfig& trace_config,
                           uint8_t modes_to_enable) {
+  // FILTERING_MODE is no longer supported.
+  DCHECK(modes_to_enable == RECORDING_MODE);
   DCHECK(trace_config.process_filter_config().IsEnabled(process_id_));
 
   AutoLock lock(lock_);
@@ -897,7 +828,6 @@
   }
 
 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
-  DCHECK(modes_to_enable == RECORDING_MODE);
   DCHECK(!trace_config.IsArgumentFilterEnabled());
 
   perfetto::TraceConfig perfetto_config;
@@ -959,38 +889,20 @@
     return;
   }
 
-  // Clear all filters from previous tracing session. These filters are not
-  // cleared at the end of tracing because some threads which hit trace event
-  // when disabling, could try to use the filters.
-  if (!enabled_modes_)
-    GetCategoryGroupFilters().clear();
-
   // Update trace config for recording.
-  const bool already_recording = enabled_modes_ & RECORDING_MODE;
-  if (modes_to_enable & RECORDING_MODE) {
-    if (already_recording) {
-      trace_config_.Merge(trace_config);
-    } else {
-      trace_config_ = trace_config;
-    }
+  const bool already_recording = enabled_;
+  if (already_recording) {
+    trace_config_.Merge(trace_config);
+  } else {
+    trace_config_ = trace_config;
   }
 
-  // Update event filters only if filtering was not enabled.
-  if (modes_to_enable & FILTERING_MODE && enabled_event_filters_.empty()) {
-    DCHECK(!trace_config.event_filters().empty());
-    enabled_event_filters_ = trace_config.event_filters();
-  }
-  // Keep the |trace_config_| updated with only enabled filters in case anyone
-  // tries to read it using |GetCurrentTraceConfig| (even if filters are
-  // empty).
-  trace_config_.SetEventFilters(enabled_event_filters_);
-
-  enabled_modes_ |= modes_to_enable;
+  enabled_ = true;
   UpdateCategoryRegistry();
 
   // Do not notify observers or create trace buffer if only enabled for
   // filtering or if recording was already enabled.
-  if (!(modes_to_enable & RECORDING_MODE) || already_recording)
+  if (already_recording)
     return;
 
   // Discard events if new trace options are different. Reducing trace buffer
@@ -1146,6 +1058,7 @@
 }
 
 void TraceLog::SetDisabledWhileLocked(uint8_t modes_to_disable) {
+  DCHECK(modes_to_disable == RECORDING_MODE);
 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
   if (!tracing_session_)
     return;
@@ -1170,8 +1083,6 @@
     tracing_session_->StopBlocking();
   }
 #else   // !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
-  if (!(enabled_modes_ & modes_to_disable))
-    return;
 
   if (dispatching_to_observers_) {
     // TODO(ssid): Change to NOTREACHED after fixing crbug.com/625170.
@@ -1180,23 +1091,10 @@
     return;
   }
 
-  bool is_recording_mode_disabled =
-      (enabled_modes_ & RECORDING_MODE) && (modes_to_disable & RECORDING_MODE);
-  enabled_modes_ &= ~modes_to_disable;
-
-  if (modes_to_disable & FILTERING_MODE)
-    enabled_event_filters_.clear();
-
-  if (modes_to_disable & RECORDING_MODE)
-    trace_config_.Clear();
-
+  enabled_ = false;
+  trace_config_.Clear();
   UpdateCategoryRegistry();
 
-  // Add metadata events and notify observers only if recording mode was
-  // disabled now.
-  if (!is_recording_mode_disabled)
-    return;
-
   AddMetadataEventsWhileLocked();
 
   // Remove metadata events so they will not get added to a subsequent trace.
@@ -1221,7 +1119,7 @@
 
 int TraceLog::GetNumTracesRecorded() {
   AutoLock lock(lock_);
-  return (enabled_modes_ & RECORDING_MODE) ? num_traces_recorded_ : -1;
+  return enabled_ ? num_traces_recorded_ : -1;
 }
 
 void TraceLog::AddEnabledStateObserver(EnabledStateObserver* listener) {
@@ -1846,28 +1744,10 @@
   }
 
   std::string console_message;
-  std::unique_ptr<TraceEvent> filtered_trace_event;
-  bool disabled_by_filters = false;
-  if (*category_group_enabled & TraceCategory::ENABLED_FOR_FILTERING) {
-    auto new_trace_event = std::make_unique<TraceEvent>(
-        thread_id, offset_event_timestamp, thread_timestamp, phase,
-        category_group_enabled, name, scope, id, bind_id, args, flags);
-
-    disabled_by_filters = true;
-    ForEachCategoryFilter(
-        category_group_enabled, [&new_trace_event, &disabled_by_filters](
-                                    TraceEventFilter* trace_event_filter) {
-          if (trace_event_filter->FilterTraceEvent(*new_trace_event))
-            disabled_by_filters = false;
-        });
-    if (!disabled_by_filters)
-      filtered_trace_event = std::move(new_trace_event);
-  }
 
   // If enabled for recording, the event should be added only if one of the
   // filters indicates or category is not enabled for filtering.
-  if ((*category_group_enabled & TraceCategory::ENABLED_FOR_RECORDING) &&
-      !disabled_by_filters) {
+  if ((*category_group_enabled & TraceCategory::ENABLED_FOR_RECORDING)) {
     OptionalAutoLock lock(&lock_);
 
     TraceEvent* trace_event = nullptr;
@@ -1880,24 +1760,21 @@
 
     // NO_THREAD_SAFETY_ANALYSIS: Conditional locking above.
     if (trace_event) {
-      if (filtered_trace_event) {
-        *trace_event = std::move(*filtered_trace_event);
-      } else {
-        trace_event->Reset(thread_id, offset_event_timestamp, thread_timestamp,
-                           phase, category_group_enabled, name, scope, id,
-                           bind_id, args, flags);
-      }
+      trace_event->Reset(thread_id, offset_event_timestamp, thread_timestamp,
+                         phase, category_group_enabled, name, scope, id,
+                         bind_id, args, flags);
+    }
 
 #if BUILDFLAG(IS_ANDROID)
       trace_event->SendToATrace();
 #endif
-    }
 
-    if (trace_options() & kInternalEchoToConsole) {
-      console_message = EventToConsoleMessage(
-          phase == TRACE_EVENT_PHASE_COMPLETE ? TRACE_EVENT_PHASE_BEGIN : phase,
-          timestamp, trace_event);
-    }
+      if (trace_options() & kInternalEchoToConsole) {
+        console_message = EventToConsoleMessage(
+            phase == TRACE_EVENT_PHASE_COMPLETE ? TRACE_EVENT_PHASE_BEGIN
+                                                : phase,
+            timestamp, trace_event);
+      }
   }
 
   if (!console_message.empty())
@@ -1976,17 +1853,6 @@
   return log.str();
 }
 
-void TraceLog::EndFilteredEvent(const unsigned char* category_group_enabled,
-                                const char* name,
-                                TraceEventHandle handle) {
-  const char* category_name = GetCategoryGroupName(category_group_enabled);
-  ForEachCategoryFilter(
-      category_group_enabled,
-      [name, category_name](TraceEventFilter* trace_event_filter) {
-        trace_event_filter->EndEvent(category_name, name);
-      });
-}
-
 void TraceLog::UpdateTraceEventDuration(
     const unsigned char* category_group_enabled,
     const char* name,
@@ -2055,9 +1921,6 @@
 
   if (!console_message.empty())
     LOG(ERROR) << console_message;
-
-  if (*category_group_enabled & TraceCategory::ENABLED_FOR_FILTERING)
-    EndFilteredEvent(category_group_enabled, name, handle);
 }
 
 uint64_t TraceLog::MangleEventId(uint64_t id) {
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 4822a494..ea5b6ea7 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -50,7 +50,6 @@
 class TraceBuffer;
 class TraceBufferChunk;
 class TraceEvent;
-class TraceEventFilter;
 class TraceEventMemoryOverhead;
 class JsonStringOutputWriter;
 
@@ -70,11 +69,10 @@
   // Argument passed to TraceLog::SetEnabled.
   enum Mode : uint8_t {
     // Enables normal tracing (recording trace events in the trace buffer).
+    // This is the only tracing mode supported now.
+    // TODO(khokhlov): Clean up all uses of tracing mode and remove this enum
+    // completely.
     RECORDING_MODE = 1 << 0,
-
-    // Trace events are enabled just for filtering but not for recording. Only
-    // event filters config of |trace_config| argument is used.
-    FILTERING_MODE = 1 << 1
   };
 
   static TraceLog* GetInstance();
@@ -90,13 +88,7 @@
   void InitializeThreadLocalEventBufferIfSupported();
 
   // See TraceConfig comments for details on how to control which categories
-  // will be traced. SetDisabled must be called distinctly for each mode that is
-  // enabled. If tracing has already been enabled for recording, category filter
-  // (enabled and disabled categories) will be merged into the current category
-  // filter. Enabling RECORDING_MODE does not enable filters. Trace event
-  // filters will be used only if FILTERING_MODE is set on |modes_to_enable|.
-  // Conversely to RECORDING_MODE, FILTERING_MODE doesn't support upgrading,
-  // i.e. filters can only be enabled if not previously enabled.
+  // will be traced. Only RECORDING_MODE is supported.
   void SetEnabled(const TraceConfig& trace_config, uint8_t modes_to_enable);
 
 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
@@ -107,11 +99,7 @@
                   const perfetto::TraceConfig& perfetto_config);
 #endif
 
-  // TODO(ssid): Remove the default SetEnabled and IsEnabled. They should take
-  // Mode as argument.
-
-  // Disables tracing for all categories for the specified |modes_to_disable|
-  // only. Only RECORDING_MODE is taken as default |modes_to_disable|.
+  // Disables tracing for all categories. Only RECORDING_MODE is supported.
   void SetDisabled();
   void SetDisabled(uint8_t modes_to_disable);
 
@@ -129,13 +117,10 @@
     return track_event_enabled_;
 #else   // !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
     AutoLock lock(lock_);
-    return enabled_modes_ & RECORDING_MODE;
+    return enabled_;
 #endif  // !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
   }
 
-  // Returns a bitmap of enabled modes from TraceLog::Mode.
-  uint8_t enabled_modes() { return enabled_modes_; }
-
   // The number of times we have begun recording traces. If tracing is off,
   // returns -1. If tracing is on, then it returns the number of times we have
   // recorded a trace. By watching for this number to increment, you can
@@ -379,10 +364,6 @@
       const TimeTicks& now,
       const ThreadTicks& thread_now);
 
-  void EndFilteredEvent(const unsigned char* category_group_enabled,
-                        const char* name,
-                        TraceEventHandle handle);
-
   ProcessId process_id() const { return process_id_; }
 
   std::unordered_map<int, std::string> process_labels() const {
@@ -393,14 +374,6 @@
   uint64_t MangleEventId(uint64_t id);
 
   // Exposed for unittesting:
-
-  // Testing factory for TraceEventFilter.
-  typedef std::unique_ptr<TraceEventFilter> (*FilterFactoryForTesting)(
-      const std::string& /* predicate_name */);
-  void SetFilterFactoryForTesting(FilterFactoryForTesting factory) {
-    filter_factory_for_testing_ = factory;
-  }
-
   // Allows clearing up our singleton instance.
   static void ResetForTesting();
 
@@ -490,13 +463,10 @@
   // Enable/disable each category group based on the current mode_,
   // category_filter_ and event_filters_enabled_.
   // Enable the category group in the recording mode if category_filter_ matches
-  // the category group, is not null. Enable category for filtering if any
-  // filter in event_filters_enabled_ enables it.
+  // the category group, is not null.
   void UpdateCategoryRegistry();
   void UpdateCategoryState(TraceCategory* category);
 
-  void CreateFiltersForTraceConfig();
-
   InternalTraceOptions GetInternalOptionsFromTraceConfig(
       const TraceConfig& config);
 
@@ -583,8 +553,8 @@
   // by thread_info_lock_) from arbitrary threads.
   mutable Lock lock_;
   Lock thread_info_lock_;
-  uint8_t enabled_modes_;  // See TraceLog::Mode.
-  int num_traces_recorded_;
+  bool enabled_{false};
+  int num_traces_recorded_{0};
   std::unique_ptr<TraceBuffer> logged_events_;
   std::vector<std::unique_ptr<TraceEvent>> metadata_events_;
 
@@ -626,7 +596,6 @@
   std::atomic<InternalTraceOptions> trace_options_;
 
   TraceConfig trace_config_;
-  TraceConfig::EventFilters enabled_event_filters_;
 
   ThreadLocalPointer<ThreadLocalEventBuffer> thread_local_event_buffer_;
   ThreadLocalBoolean thread_blocks_message_loop_;
@@ -669,8 +638,6 @@
 #endif  // !BUILDFLAG(IS_NACL)
 #endif  // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 
-  FilterFactoryForTesting filter_factory_for_testing_ = nullptr;
-
 #if BUILDFLAG(IS_ANDROID)
   absl::optional<TraceConfig> atrace_startup_config_;
 #endif
diff --git a/build/android/gyp/apkbuilder.py b/build/android/gyp/apkbuilder.py
index cc1f1ac7..57c7c4eb 100755
--- a/build/android/gyp/apkbuilder.py
+++ b/build/android/gyp/apkbuilder.py
@@ -112,6 +112,8 @@
       '--library-always-compress',
       action='append',
       help='The list of library files that we always compress.')
+  # TODO(crbug.com/1337134): Remove all references to --library-renames.
+  # Setting it should be a no-op.
   parser.add_argument(
       '--library-renames',
       action='append',
@@ -250,7 +252,7 @@
 
 
 def _GetNativeLibrariesToAdd(native_libs, android_abi, uncompress, fast_align,
-                             lib_always_compress, lib_renames):
+                             lib_always_compress):
   """Returns the list of file_detail tuples for native libraries in the apk.
 
   Returns: A list of (src_path, apk_path, compress, alignment) tuple
@@ -263,10 +265,6 @@
     basename = os.path.basename(path)
     compress = not uncompress or any(lib_name in basename
                                      for lib_name in lib_always_compress)
-    rename = any(lib_name in basename for lib_name in lib_renames)
-    if rename:
-      basename = 'crazy.' + basename
-
     lib_android_abi = android_abi
     if path.startswith('android_clang_arm64_hwasan/'):
       lib_android_abi = 'arm64-v8a-hwasan'
@@ -371,15 +369,16 @@
                         allow_reads=allow_reads))
     return ret
 
-  libs_to_add = _GetNativeLibrariesToAdd(
-      native_libs, options.android_abi, options.uncompress_shared_libraries,
-      fast_align, options.library_always_compress, options.library_renames)
+  libs_to_add = _GetNativeLibrariesToAdd(native_libs, options.android_abi,
+                                         options.uncompress_shared_libraries,
+                                         fast_align,
+                                         options.library_always_compress)
   if options.secondary_android_abi:
     libs_to_add.extend(
-        _GetNativeLibrariesToAdd(
-            secondary_native_libs, options.secondary_android_abi,
-            options.uncompress_shared_libraries, fast_align,
-            options.library_always_compress, options.library_renames))
+        _GetNativeLibrariesToAdd(secondary_native_libs,
+                                 options.secondary_android_abi,
+                                 options.uncompress_shared_libraries,
+                                 fast_align, options.library_always_compress))
 
   if options.expected_file:
     # We compute expectations without reading the files. This allows us to check
diff --git a/chrome/android/expectations/chrome_modern_public_bundle.arm.libs_and_assets.expected b/chrome/android/expectations/chrome_modern_public_bundle.arm.libs_and_assets.expected
index cfbca51..6658d67 100644
--- a/chrome/android/expectations/chrome_modern_public_bundle.arm.libs_and_assets.expected
+++ b/chrome/android/expectations/chrome_modern_public_bundle.arm.libs_and_assets.expected
@@ -1,4 +1,4 @@
-apk_path=lib/armeabi-v7a/crazy.libchrome.so, compress=False, alignment=4096
+apk_path=lib/armeabi-v7a/libchrome.so, compress=False, alignment=4096
 apk_path=lib/armeabi-v7a/libchrome_crashpad_handler.so, compress=True, alignment=0
 apk_path=lib/armeabi-v7a/libchromium_android_linker.so, compress=True, alignment=0
 apk_path=assets/chrome_100_percent.pak, compress=False, alignment=4
diff --git a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
index 3ea319e8..249a58ab 100644
--- a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
+++ b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
@@ -37,7 +37,6 @@
 
 using base::android::ConvertJavaStringToUTF8;
 using base::android::ConvertUTF8ToJavaString;
-using base::android::JavaByteArrayToByteVector;
 using base::android::JavaParamRef;
 using base::android::JavaRef;
 using base::android::ScopedJavaGlobalRef;
@@ -214,41 +213,17 @@
   kMaxValue = 12,
 };
 
-// JavaByteArrayToSpan returns a span that aliases |data|. Be aware that the
-// reference for |data| must outlive the span.
-base::span<const uint8_t> JavaByteArrayToSpan(
+// JavaByteArrayToByteVector returns a copy of the contents of |data|.
+std::vector<uint8_t> JavaByteArrayToByteVector(
     JNIEnv* env,
     const JavaParamRef<jbyteArray>& data) {
   if (data.is_null()) {
-    return base::span<const uint8_t>();
+    return std::vector<uint8_t>();
   }
 
-  const size_t data_len = env->GetArrayLength(data);
-  const jbyte* data_bytes = env->GetByteArrayElements(data, /*iscopy=*/nullptr);
-  return base::as_bytes(base::make_span(data_bytes, data_len));
-}
-
-// JavaByteArrayToFixedSpan returns a span that aliases |data|, or |nullopt| if
-// the span is not of the correct length. Be aware that the reference for |data|
-// must outlive the span.
-template <size_t N>
-absl::optional<base::span<const uint8_t, N>> JavaByteArrayToFixedSpan(
-    JNIEnv* env,
-    const JavaParamRef<jbyteArray>& data) {
-  static_assert(N != 0,
-                "Zero case is different from JavaByteArrayToSpan as null "
-                "inputs will always be rejected here.");
-
-  if (data.is_null()) {
-    return absl::nullopt;
-  }
-
-  const size_t data_len = env->GetArrayLength(data);
-  if (data_len != N) {
-    return absl::nullopt;
-  }
-  const jbyte* data_bytes = env->GetByteArrayElements(data, /*iscopy=*/nullptr);
-  return base::as_bytes(base::make_span<N>(data_bytes, data_len));
+  std::vector<uint8_t> ret;
+  base::android::JavaByteArrayToByteVector(env, data, &ret);
+  return ret;
 }
 
 // GlobalData holds all the state for ongoing security key operations. Since
@@ -629,7 +604,7 @@
   // The root_secret may not be provided when triggered for server-link. It
   // won't be used in that case either, but we need to be able to grab it if
   // setup() is called called for a different type of exchange.
-  base::span<const uint8_t> root_secret = JavaByteArrayToSpan(env, secret);
+  std::vector<uint8_t> root_secret = JavaByteArrayToByteVector(env, secret);
   if (!root_secret.empty() && !global_data.root_secret) {
     global_data.root_secret.emplace();
     CHECK_EQ(global_data.root_secret->size(), root_secret.size());
@@ -735,16 +710,18 @@
                     const JavaParamRef<jbyteArray>& server_link_data_java) {
   constexpr size_t kDataSize =
       device::kP256X962Length + device::cablev2::kQRSecretSize;
-  const absl::optional<base::span<const uint8_t, kDataSize>> server_link_data =
-      JavaByteArrayToFixedSpan<kDataSize>(env, server_link_data_java);
+  const std::vector<uint8_t> server_link_data_vec =
+      JavaByteArrayToByteVector(env, server_link_data_java);
   // validateServerLinkData should have been called to check this already.
-  CHECK(server_link_data);
+  CHECK_EQ(server_link_data_vec.size(), kDataSize);
+  base::span<const uint8_t> server_link_data =
+      base::make_span(server_link_data_vec);
 
   const base::span<const uint8_t, device::kP256X962Length> peer_identity =
-      server_link_data->subspan<0, device::kP256X962Length>();
+      server_link_data.subspan<0, device::kP256X962Length>();
   const base::span<const uint8_t, device::cablev2::kQRSecretSize> qr_secret =
       server_link_data
-          ->subspan<device::kP256X962Length, device::cablev2::kQRSecretSize>();
+          .subspan<device::kP256X962Length, device::cablev2::kQRSecretSize>();
   const std::array<uint8_t, device::cablev2::kTunnelIdSize> tunnel_id =
       device::cablev2::Derive<device::cablev2::kTunnelIdSize>(
           qr_secret, base::span<uint8_t>(),
@@ -793,7 +770,7 @@
 
   auto event =
       device::cablev2::authenticator::Registration::Event::FromSerialized(
-          JavaByteArrayToSpan(env, serialized_event));
+          JavaByteArrayToByteVector(env, serialized_event));
   if (!event) {
     LOG(ERROR) << "Failed to parse event";
     return 0;
@@ -830,14 +807,15 @@
 static int JNI_CableAuthenticator_ValidateServerLinkData(
     JNIEnv* env,
     const JavaParamRef<jbyteArray>& jdata) {
-  base::span<const uint8_t> data = JavaByteArrayToSpan(env, jdata);
+  std::vector<uint8_t> data = JavaByteArrayToByteVector(env, jdata);
   if (data.size() != device::kP256X962Length + device::cablev2::kQRSecretSize) {
     RecordResult(nullptr, CableV2MobileResult::kInvalidServerLink);
     return static_cast<int>(device::cablev2::authenticator::Platform::Error::
                                 SERVER_LINK_WRONG_LENGTH);
   }
 
-  base::span<const uint8_t> x962 = data.subspan(0, device::kP256X962Length);
+  base::span<const uint8_t> x962 =
+      base::make_span(data).subspan(0, device::kP256X962Length);
   bssl::UniquePtr<EC_GROUP> p256(
       EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
   bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
@@ -887,14 +865,14 @@
   auto callback = std::move(*global_data.pending_make_credential_callback);
   global_data.pending_make_credential_callback.reset();
 
-  absl::optional<base::span<const uint8_t>> device_public_key_signature;
+  absl::optional<std::vector<uint8_t>> device_public_key_signature;
   if (jdevice_public_key_signature) {
     device_public_key_signature =
-        JavaByteArrayToSpan(env, jdevice_public_key_signature);
+        JavaByteArrayToByteVector(env, jdevice_public_key_signature);
   }
 
   std::move(callback).Run(ctap_status,
-                          JavaByteArrayToSpan(env, jattestation_object),
+                          JavaByteArrayToByteVector(env, jattestation_object),
                           device_public_key_signature);
 }
 
@@ -914,8 +892,8 @@
 
   if (ctap_status ==
       static_cast<jint>(device::CtapDeviceResponseCode::kSuccess)) {
-    base::span<const uint8_t> response_bytes =
-        JavaByteArrayToSpan(env, jresponse_bytes);
+    std::vector<uint8_t> response_bytes =
+        JavaByteArrayToByteVector(env, jresponse_bytes);
     auto response = blink::mojom::GetAssertionAuthenticatorResponse::New();
     if (blink::mojom::GetAssertionAuthenticatorResponse::Deserialize(
             response_bytes.data(), response_bytes.size(), &response)) {
@@ -954,6 +932,6 @@
   if (!usb_data) {
     global_data.usb_callback->Run(absl::nullopt);
   } else {
-    global_data.usb_callback->Run(JavaByteArrayToSpan(env, usb_data));
+    global_data.usb_callback->Run(JavaByteArrayToByteVector(env, usb_data));
   }
 }
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index e0ea6afa..4446cbd 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -67,8 +67,6 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.homepage.HomepageManager;
 import org.chromium.chrome.browser.layouts.LayoutType;
-import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
-import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
 import org.chromium.chrome.browser.suggestions.mostvisited.MostVisitedSitesMetadataUtils;
 import org.chromium.chrome.browser.suggestions.tile.Tile;
@@ -499,29 +497,6 @@
         testShowLastTabAtStartUp();
     }
 
-    @Test
-    @MediumTest
-    @Feature({"RenderTest"})
-    // clang-format off
-    @EnableFeatures({ChromeFeatureList.FEED_ABLATION,
-        ChromeFeatureList.START_SURFACE_DISABLED_FEED_IMPROVEMENT,
-        ChromeFeatureList.TAB_SWITCHER_ON_RETURN,
-        ChromeFeatureList.START_SURFACE_ANDROID})
-    public void renderImprovingStartSurfaceWhenFeedDisabled() throws IOException {
-        // clang-format on
-        StartSurfaceTestUtils.setMVTiles(mSuggestionsDeps);
-        SharedPreferencesManager.getInstance().writeBoolean(
-                ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE, false);
-        mActivityTestRule.startMainActivityFromLauncher();
-        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
-        Assert.assertTrue(ReturnToChromeUtil.shouldImproveStartWhenFeedIsDisabled(cta));
-        StartSurfaceTestUtils.waitForOverviewVisible(cta);
-
-        View surface = cta.findViewById(R.id.primary_tasks_surface_view);
-        ChromeRenderTestRule.sanitize(surface);
-        mRenderTestRule.render(surface, "start_surface_no_feed_improvement");
-    }
-
     private void testShowLastTabAtStartUp() throws IOException {
         StartSurfaceTestUtils.createTabStateFile(new int[] {0});
         StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0);
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index 838c565..d860a8d 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -33,13 +33,11 @@
     "java/res/drawable/fake_search_box_text_box_bg_incognito.xml",
     "java/res/drawable/ic_check_googblue_20dp_animated.xml",
     "java/res/drawable/ic_close_tabs_24dp.xml",
-    "java/res/drawable/ic_deselect_all_24dp.xml",
     "java/res/drawable/ic_group_icon_16dp.xml",
     "java/res/drawable/ic_price_alert_blue.xml",
     "java/res/drawable/ic_rating_star_full.xml",
     "java/res/drawable/ic_rating_star_half.xml",
     "java/res/drawable/ic_rating_star_outline.xml",
-    "java/res/drawable/ic_select_all_24dp.xml",
     "java/res/drawable/iph_drag_and_drop_animated_drawable.xml",
     "java/res/drawable/iph_drag_and_drop_drawable.xml",
     "java/res/drawable/price_card_background.xml",
diff --git a/chrome/android/features/tab_ui/java/res/drawable/ic_deselect_all_24dp.xml b/chrome/android/features/tab_ui/java/res/drawable/ic_deselect_all_24dp.xml
deleted file mode 100644
index d183fc4..0000000
--- a/chrome/android/features/tab_ui/java/res/drawable/ic_deselect_all_24dp.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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. -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="@macro/default_icon_color">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M7.94,5.12L6.49,3.66C8.07,2.61 9.96,2 12,2c5.52,0 10,4.48 10,10c0,2.04 -0.61,3.93 -1.66,5.51l-1.46,-1.46C19.59,14.86 20,13.48 20,12c0,-4.41 -3.59,-8 -8,-8C10.52,4 9.14,4.41 7.94,5.12zM17.66,9.53l-1.41,-1.41l-2.65,2.65l1.41,1.41L17.66,9.53zM19.78,22.61l-2.27,-2.27C15.93,21.39 14.04,22 12,22C6.48,22 2,17.52 2,12c0,-2.04 0.61,-3.93 1.66,-5.51L1.39,4.22l1.41,-1.41l18.38,18.38L19.78,22.61zM16.06,18.88l-3.88,-3.88l-1.59,1.59l-4.24,-4.24l1.41,-1.41l2.83,2.83l0.18,-0.18L5.12,7.94C4.41,9.14 4,10.52 4,12c0,4.41 3.59,8 8,8C13.48,20 14.86,19.59 16.06,18.88z"/>
-</vector>
diff --git a/chrome/android/features/tab_ui/java/res/drawable/ic_select_all_24dp.xml b/chrome/android/features/tab_ui/java/res/drawable/ic_select_all_24dp.xml
deleted file mode 100644
index 427d9b60..0000000
--- a/chrome/android/features/tab_ui/java/res/drawable/ic_select_all_24dp.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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. -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="@macro/default_icon_color">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM10,14.2l-2.6,-2.6L6,13l4,4 8,-8 -1.4,-1.4z"/>
-</vector>
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 ba200e1..fe50f99 100644
--- a/chrome/android/features/tab_ui/java/res/values/dimens.xml
+++ b/chrome/android/features/tab_ui/java/res/values/dimens.xml
@@ -81,5 +81,6 @@
 
     <!-- Dimens for TabSelectionEditorV2 -->
     <dimen name="tab_selection_editor_action_view_padding">14dp</dimen>
+    <dimen name="tab_selection_editor_selection_action_inset">2dp</dimen>
     <dimen name="tab_selection_editor_share_sheet_preview_thumbnail_padding">10dp</dimen>
 </resources>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index c5f71ec..c77e9bb 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -556,8 +556,9 @@
 
         if (TabUiFeatureUtilities.isTabSelectionEditorV2Enabled(mContext)) {
             List<TabSelectionEditorAction> actions = new ArrayList<>();
-            actions.add(TabSelectionEditorSelectionAction.createAction(
-                    mContext, ShowMode.MENU_ONLY, ButtonType.ICON_AND_TEXT, IconPosition.END));
+            actions.add(TabSelectionEditorSelectionAction.createAction(mContext, ShowMode.MENU_ONLY,
+                    ButtonType.ICON_AND_TEXT, IconPosition.END,
+                    mTabModelSelector.getCurrentModel().isIncognito()));
             actions.add(TabSelectionEditorCloseAction.createAction(
                     mContext, ShowMode.MENU_ONLY, ButtonType.ICON_AND_TEXT, IconPosition.START));
             actions.add(TabSelectionEditorUngroupAction.createAction(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorAction.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorAction.java
index 9189e22..9d20979f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorAction.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorAction.java
@@ -159,10 +159,13 @@
                                 ColorStateList.valueOf(Color.TRANSPARENT))
                         .with(TabSelectionEditorActionProperties.ICON_TINT,
                                 ColorStateList.valueOf(Color.TRANSPARENT))
+                        .with(TabSelectionEditorActionProperties.SKIP_ICON_TINT, false)
                         .with(TabSelectionEditorActionProperties.ON_CLICK_LISTENER, this::perform)
                         .with(TabSelectionEditorActionProperties.SHOULD_DISMISS_MENU, true)
                         .with(TabSelectionEditorActionProperties.ON_SELECTION_STATE_CHANGE,
                                 this::onSelectionStateChange)
+                        .with(TabSelectionEditorActionProperties.ON_SHOWN_IN_MENU,
+                                this::onShownInMenu)
                         .build();
 
         if (contentDescriptionResourceId == null) return;
@@ -196,6 +199,8 @@
         return true;
     }
 
+    public void onShownInMenu() {}
+
     /**
      * @return Whether the TabSelectionEditor supports applying the actions to related tabs.
      */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProperties.java
index cd7d144..b4484258 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProperties.java
@@ -41,29 +41,38 @@
             new WritableObjectPropertyKey<>();
 
     /**
-     * Tint for the icon.
+     * Tint for the icon. This should be null if {@code SKIP_ICON_TINT} is true.
      */
     public static final WritableObjectPropertyKey<ColorStateList> ICON_TINT =
             new WritableObjectPropertyKey<>();
 
+    /**
+     * If true skip usage of the {@code ICON_TINT} property. Used if the icon tint is handled
+     * directly by the {@link TabSelectionEditorAction}.
+     */
+    public static final WritableBooleanPropertyKey SKIP_ICON_TINT =
+            new WritableBooleanPropertyKey();
+
     public static final WritableObjectPropertyKey<Runnable> ON_CLICK_LISTENER =
             new WritableObjectPropertyKey<>();
     public static final WritableBooleanPropertyKey SHOULD_DISMISS_MENU =
             new WritableBooleanPropertyKey();
     public static final WritableObjectPropertyKey<Callback<List<Integer>>>
             ON_SELECTION_STATE_CHANGE = new WritableObjectPropertyKey<>();
+    public static final WritableObjectPropertyKey<Runnable> ON_SHOWN_IN_MENU =
+            new WritableObjectPropertyKey<>();
 
     /**
      * Keys for the {@link TabSelectionEditorAction}.
      */
     public static final PropertyKey[] ACTION_KEYS = {MENU_ITEM_ID, SHOW_MODE, BUTTON_TYPE,
             ICON_POSITION, TITLE_RESOURCE_ID, TITLE_IS_PLURAL, CONTENT_DESCRIPTION_RESOURCE_ID,
-            ICON, ENABLED, ITEM_COUNT, TEXT_TINT, ICON_TINT, ON_CLICK_LISTENER, SHOULD_DISMISS_MENU,
-            ON_SELECTION_STATE_CHANGE};
+            ICON, ENABLED, ITEM_COUNT, TEXT_TINT, ICON_TINT, SKIP_ICON_TINT, ON_CLICK_LISTENER,
+            SHOULD_DISMISS_MENU, ON_SELECTION_STATE_CHANGE, ON_SHOWN_IN_MENU};
 
     /**
      * Keys for the {@link TabSelectionEditorMenuItem}.
      */
-    public static final PropertyKey[] MENU_ITEM_KEYS = {
-            MENU_ITEM_ID, TITLE, CONTENT_DESCRIPTION, ICON, ICON_TINT, ENABLED, ITEM_COUNT};
+    public static final PropertyKey[] MENU_ITEM_KEYS = {MENU_ITEM_ID, TITLE, CONTENT_DESCRIPTION,
+            ICON, ICON_TINT, ENABLED, ITEM_COUNT, ON_SHOWN_IN_MENU};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionViewLayout.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionViewLayout.java
index acfb435..e331abb 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionViewLayout.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionViewLayout.java
@@ -80,9 +80,12 @@
     }
 
     /**
+     * @param popupListener for handling show events.
      * @param delegate for handling menu button presses.
      */
-    public void setListMenuButtonDelegate(ListMenuButtonDelegate delegate) {
+    public void setListMenuButtonDelegate(
+            ListMenuButton.PopupMenuShownListener popupListener, ListMenuButtonDelegate delegate) {
+        mMenuButton.addPopupListener(popupListener);
         mMenuButton.setDelegate(delegate);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
index 3a5815fb..17be223 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMediator.java
@@ -183,7 +183,10 @@
         if (mActionListModel == null) return;
 
         for (PropertyModel model : mActionListModel) {
+            // TODO(ckitagawa): update these tints with input from UX.
             model.set(TabSelectionEditorActionProperties.TEXT_TINT, toolbarTintColorList);
+            if (model.get(TabSelectionEditorActionProperties.SKIP_ICON_TINT)) continue;
+
             model.set(TabSelectionEditorActionProperties.ICON_TINT, toolbarTintColorList);
         }
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenu.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenu.java
index b4915d03..7ae2503 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenu.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenu.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabSelectionEditorActionViewLayout.ActionViewLayoutDelegate;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.ui.modelutil.LayoutViewBuilder;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
@@ -35,9 +36,9 @@
  * {@link TabSelectionEditorActionViewLayout} for Action views. The menu contains a list of
  * {@link TabSelectionEditorMenuItem}s which hold optional action views if room is available.
  */
-public class TabSelectionEditorMenu implements ListMenu, OnItemClickListener,
-                                               SelectionDelegate.SelectionObserver<Integer>,
-                                               ActionViewLayoutDelegate {
+public class TabSelectionEditorMenu
+        implements ListMenu, OnItemClickListener, SelectionDelegate.SelectionObserver<Integer>,
+                   ActionViewLayoutDelegate, ListMenuButton.PopupMenuShownListener {
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ListItemType.MENU_ITEM})
     public static @interface ListItemType {
@@ -84,7 +85,7 @@
         mListView.setDivider(null);
         mListView.setOnItemClickListener(this);
 
-        mActionViewLayout.setListMenuButtonDelegate(() -> this);
+        mActionViewLayout.setListMenuButtonDelegate(this, () -> this);
     }
 
     private void registerItemTypes() {
@@ -95,6 +96,13 @@
         // clang-format on
     }
 
+    @Override
+    public void onPopupMenuShown() {
+        for (ListItem listItem : mModelList) {
+            listItem.model.get(TabSelectionEditorActionProperties.ON_SHOWN_IN_MENU).run();
+        }
+    }
+
     private ListItem buildListItem(int menuItemId) {
         // Model values are populated while configuring the TabSelectionEditorMenuItem.
         return new ListItem(ListItemType.MENU_ITEM,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuAdapter.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuAdapter.java
index 57baf1402..a9c5ac19d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuAdapter.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuAdapter.java
@@ -109,6 +109,9 @@
         } else if (key == TabSelectionEditorActionProperties.ON_SELECTION_STATE_CHANGE) {
             menuItem.setOnSelectionStateChange(
                     actionModel.get(TabSelectionEditorActionProperties.ON_SELECTION_STATE_CHANGE));
+        } else if (key == TabSelectionEditorActionProperties.ON_SHOWN_IN_MENU) {
+            menuItem.setOnShownInMenu(
+                    actionModel.get(TabSelectionEditorActionProperties.ON_SHOWN_IN_MENU));
         }
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuItem.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuItem.java
index 84eedd3..f8bfaa5 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuItem.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuItem.java
@@ -154,6 +154,15 @@
     }
 
     public void setIconTint(@Nullable ColorStateList colorStateList) {
+        // A null colorStateList is used with TabSelectionEditorActionProperties.SKIP_ICON_TINT
+        // = true to signal that a custom tint is used. Ignore null so that this custom tint is
+        // not overridden.
+        if (colorStateList == null) {
+            mIconTint = null;
+            mListItem.model.set(TabSelectionEditorActionProperties.ICON_TINT, null);
+            return;
+        }
+
         // mListItem uses the default icon tint whenever shown. Cache the tint to restore it when
         // the action view shown state is toggled.
         mListItem.model.set(TabSelectionEditorActionProperties.ICON_TINT,
@@ -192,6 +201,10 @@
         mOnSelectionStateChange = callback;
     }
 
+    public void setOnShownInMenu(Runnable runnable) {
+        mListItem.model.set(TabSelectionEditorActionProperties.ON_SHOWN_IN_MENU, runnable);
+    }
+
     /**
      * Handler for click events on the menu item or action view.
      */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionAction.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionAction.java
index 5bf36a2..5a245a9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionAction.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionAction.java
@@ -5,11 +5,15 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
 
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.tab.Tab;
@@ -23,10 +27,11 @@
  * Select all and deselect all toggle action for the {@link TabSelectionEditorMenu}.
  */
 public class TabSelectionEditorSelectionAction extends TabSelectionEditorAction {
+    private static final int BACKGROUND = 0;
+    private static final int CHECKMARK = 1;
+
     private Context mContext;
     private @ActionState int mActionState;
-    private final Drawable mSelectAllIcon;
-    private final Drawable mDeselectAllIcon;
 
     @IntDef({ActionState.UNKNOWN, ActionState.SELECT_ALL, ActionState.DESELECT_ALL})
     @Retention(RetentionPolicy.SOURCE)
@@ -45,28 +50,42 @@
      * @param isIncognito whether the current tab model is incognito this will update dynamically.
      */
     public static TabSelectionEditorAction createAction(Context context, @ShowMode int showMode,
-            @ButtonType int buttonType, @IconPosition int iconPosition) {
-        Drawable selectAllIcon =
-                AppCompatResources.getDrawable(context, R.drawable.ic_select_all_24dp);
-        Drawable deselectAllIcon =
-                AppCompatResources.getDrawable(context, R.drawable.ic_deselect_all_24dp);
+            @ButtonType int buttonType, @IconPosition int iconPosition, boolean isIncognito) {
         return new TabSelectionEditorSelectionAction(
-                context, showMode, buttonType, iconPosition, selectAllIcon, deselectAllIcon);
+                context, showMode, buttonType, iconPosition, isIncognito, buildDrawable(context));
     }
 
     @VisibleForTesting
     TabSelectionEditorSelectionAction(Context context, @ShowMode int showMode,
-            @ButtonType int buttonType, @IconPosition int iconPosition, Drawable selectAllIcon,
-            Drawable deselectAllIcon) {
+            @ButtonType int buttonType, @IconPosition int iconPosition, boolean isIncognito,
+            Drawable drawable) {
         super(R.id.tab_selection_editor_selection_menu_item, showMode, buttonType, iconPosition,
-                R.string.tab_selection_editor_select_all, null, selectAllIcon);
+                R.string.tab_selection_editor_select_all, null, drawable);
 
         mContext = context;
         mActionState = ActionState.UNKNOWN;
-        mSelectAllIcon = selectAllIcon;
-        mDeselectAllIcon = deselectAllIcon;
+        getPropertyModel().set(TabSelectionEditorActionProperties.ICON_TINT, null);
+        getPropertyModel().set(TabSelectionEditorActionProperties.SKIP_ICON_TINT, true);
         getPropertyModel().set(TabSelectionEditorActionProperties.SHOULD_DISMISS_MENU, false);
-        updateState(ActionState.SELECT_ALL);
+        updateState(ActionState.SELECT_ALL, isIncognito);
+        LayerDrawable layers =
+                (LayerDrawable) getPropertyModel().get(TabSelectionEditorActionProperties.ICON);
+        layers.setCallback(new Drawable.Callback() {
+            @Override
+            public void invalidateDrawable(Drawable who) {
+                // No-op.
+            }
+
+            @Override
+            public void scheduleDrawable(Drawable who, Runnable what, long when) {
+                who.invalidateSelf();
+            }
+
+            @Override
+            public void unscheduleDrawable(Drawable who, Runnable what) {
+                who.unscheduleSelf(what);
+            }
+        });
     }
 
     @Override
@@ -75,10 +94,16 @@
     }
 
     @Override
+    public void onShownInMenu() {
+        updateDrawable();
+    }
+
+    @Override
     public void onSelectionStateChange(List<Integer> tabIds) {
         setEnabledAndItemCount(true, tabIds.size());
         updateState(getActionDelegate().areAllTabsSelected() ? ActionState.DESELECT_ALL
-                                                             : ActionState.SELECT_ALL);
+                                                             : ActionState.SELECT_ALL,
+                getTabModelSelector().getCurrentModel().isIncognito());
     }
 
     @Override
@@ -100,21 +125,64 @@
         return false;
     }
 
-    private void updateState(@ActionState int selectionState) {
+    private void updateState(@ActionState int selectionState, boolean isIncognito) {
         if (mActionState == selectionState) return;
 
         mActionState = selectionState;
+        LayerDrawable layers =
+                (LayerDrawable) getPropertyModel().get(TabSelectionEditorActionProperties.ICON);
 
         if (mActionState == ActionState.SELECT_ALL) {
             getPropertyModel().set(TabSelectionEditorActionProperties.TITLE_RESOURCE_ID,
                     R.string.tab_selection_editor_select_all);
-            getPropertyModel().set(TabSelectionEditorActionProperties.ICON, mSelectAllIcon);
+            updateDrawable();
         } else if (mActionState == ActionState.DESELECT_ALL) {
             getPropertyModel().set(TabSelectionEditorActionProperties.TITLE_RESOURCE_ID,
                     R.string.tab_selection_editor_deselect_all);
-            getPropertyModel().set(TabSelectionEditorActionProperties.ICON, mDeselectAllIcon);
+            updateDrawable();
         } else {
             assert false : "Invalid selection state";
         }
     }
+
+    private void updateDrawable() {
+        LayerDrawable layers =
+                (LayerDrawable) getPropertyModel().get(TabSelectionEditorActionProperties.ICON);
+        if (mActionState == ActionState.SELECT_ALL) {
+            layers.getDrawable(BACKGROUND)
+                    .setLevel(
+                            mContext.getResources().getInteger(R.integer.list_item_level_default));
+
+            layers.getDrawable(CHECKMARK).setAlpha(0);
+            layers.getDrawable(CHECKMARK).setTint(Color.TRANSPARENT);
+            layers.invalidateSelf();
+        } else if (mActionState == ActionState.DESELECT_ALL) {
+            layers.getDrawable(BACKGROUND)
+                    .setLevel(
+                            mContext.getResources().getInteger(R.integer.list_item_level_selected));
+
+            layers.getDrawable(CHECKMARK).setAlpha(255);
+            layers.getDrawable(CHECKMARK).setTint(
+                    TabUiThemeProvider.getSelectionActionIconCheckedDrawableColor(mContext));
+            layers.invalidateSelf();
+            ((AnimatedVectorDrawableCompat) layers.getDrawable(CHECKMARK)).start();
+        } else {
+            assert false : "Invalid selection state";
+        }
+    }
+
+    private static Drawable buildDrawable(Context context) {
+        Drawable[] drawables = new Drawable[2];
+
+        Drawable selectionListIcon = ResourcesCompat.getDrawable(context.getResources(),
+                R.drawable.tab_grid_selection_list_icon, context.getTheme());
+        drawables[BACKGROUND] = new InsetDrawable(selectionListIcon,
+                (int) context.getResources().getDimension(
+                        R.dimen.tab_selection_editor_selection_action_inset));
+        drawables[BACKGROUND].setTint(
+                TabUiThemeProvider.getSelectionActionIconBackgroundColor(context));
+        drawables[CHECKMARK] = AnimatedVectorDrawableCompat.create(
+                context, R.drawable.ic_check_googblue_20dp_animated);
+        return new LayerDrawable(drawables);
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index 30261d2..1d03626 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -499,8 +499,9 @@
 
         if (mTabSelectionEditorActions == null) {
             mTabSelectionEditorActions = new ArrayList<>();
-            mTabSelectionEditorActions.add(TabSelectionEditorSelectionAction.createAction(
-                    mActivity, ShowMode.MENU_ONLY, ButtonType.ICON_AND_TEXT, IconPosition.END));
+            mTabSelectionEditorActions.add(TabSelectionEditorSelectionAction.createAction(mActivity,
+                    ShowMode.MENU_ONLY, ButtonType.ICON_AND_TEXT, IconPosition.END,
+                    mTabModelSelector.getCurrentModel().isIncognito()));
             mTabSelectionEditorActions.add(TabSelectionEditorCloseAction.createAction(
                     mActivity, ShowMode.MENU_ONLY, ButtonType.ICON_AND_TEXT, IconPosition.START));
             mTabSelectionEditorActions.add(TabSelectionEditorGroupAction.createAction(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
index 6b60a8e..e0ec536 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
@@ -187,6 +187,28 @@
     }
 
     /**
+     * Returns the {@link ColorInt} to use for the {@link TabSelectionEditorSelectionAction}
+     * icon background.
+     *
+     * @param context {@link Context} used to retrieve color.
+     * @return The {@link ColorInt} for select all icon background.
+     */
+    public static @ColorInt int getSelectionActionIconBackgroundColor(Context context) {
+        return MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG);
+    }
+
+    /**
+     * Returns the {@link ColorInt} to use for the "check" drawable on the
+     * {@link TabSelectionEditorSelectionAction}.
+     *
+     * @param context {@link Context} used to retrieve color.
+     * @return The {@link ColorInt} for "check" drawable.
+     */
+    public static @ColorInt int getSelectionActionIconCheckedDrawableColor(Context context) {
+        return MaterialColors.getColor(context, org.chromium.chrome.R.attr.colorOnPrimary, TAG);
+    }
+
+    /**
      * Returns the thumbnail placeholder color resource id based on the incognito mode.
      *
      * @param isIncognito Whether the color is used for incognito mode.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuTest.java
index a83466e..14115fa4 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuTest.java
@@ -102,8 +102,8 @@
     public RenderTestRule mRenderTestRule =
             RenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(Component.UI_BROWSER_MOBILE_TAB_SWITCHER_GRID)
-                    .setRevision(4)
-                    .setDescription("New selection icons")
+                    .setRevision(3)
+                    .setDescription("Pluralize strings")
                     .build();
 
     @Rule
@@ -321,8 +321,8 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             actions.add(new FakeTabSelectionEditorAction(getActivity(),
                     R.id.tab_selection_editor_close_menu_item, ShowMode.IF_ROOM, ButtonType.TEXT,
-                    IconPosition.END, R.string.tab_selection_editor_select_all,
-                    R.drawable.ic_select_all_24dp));
+                    IconPosition.END, R.plurals.tab_selection_editor_close_tabs,
+                    R.drawable.ic_close_tabs_24dp));
             configureMenuWithActions(actions);
         });
 
@@ -359,8 +359,8 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             actions.add(new FakeTabSelectionEditorAction(getActivity(),
                     R.id.tab_selection_editor_close_menu_item, ShowMode.MENU_ONLY, ButtonType.TEXT,
-                    IconPosition.START, R.string.tab_selection_editor_deselect_all,
-                    R.drawable.ic_deselect_all_24dp));
+                    IconPosition.START, R.plurals.tab_selection_editor_close_tabs,
+                    R.drawable.ic_close_tabs_24dp));
             configureMenuWithActions(actions);
         });
 
@@ -370,7 +370,7 @@
 
         PopupListener listener = new PopupListener();
         openMenu(listener);
-        assertMenuItem("Deselect all", false);
+        assertMenuItem("Close tabs", false);
 
         forceFinishRollAnimation();
         mRenderTestRule.render(
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
index e3629a2..8173d39 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
@@ -136,8 +136,8 @@
     public ChromeRenderTestRule mRenderTestRule =
             ChromeRenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_TAB_SWITCHER)
-                    .setRevision(5)
-                    .setDescription("TabSelectionEditorV2 New selection icons")
+                    .setRevision(4)
+                    .setDescription("TabSelectionEditorV2 UI Polish")
                     .build();
 
     @Mock
@@ -178,6 +178,7 @@
 
     @After
     public void tearDown() {
+        TabSelectionEditorShareAction.setIntentCallbackForTesting(null);
         if (mTabSelectionEditorCoordinator != null) {
             if (sActivityTestRule.getActivity().findViewById(R.id.app_menu_list) != null) {
                 Espresso.pressBack();
@@ -873,9 +874,9 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             List<TabSelectionEditorAction> actions = new ArrayList<>();
-            actions.add(
-                    TabSelectionEditorSelectionAction.createAction(sActivityTestRule.getActivity(),
-                            ShowMode.IF_ROOM, ButtonType.ICON_AND_TEXT, IconPosition.END));
+            actions.add(TabSelectionEditorSelectionAction.createAction(
+                    sActivityTestRule.getActivity(), ShowMode.IF_ROOM, ButtonType.ICON_AND_TEXT,
+                    IconPosition.END, /*isIncognito=*/false));
 
             mTabSelectionEditorController.configureToolbarWithMenuItems(actions, null);
         });
@@ -1080,9 +1081,9 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             List<TabSelectionEditorAction> actions = new ArrayList<>();
-            actions.add(
-                    TabSelectionEditorSelectionAction.createAction(sActivityTestRule.getActivity(),
-                            ShowMode.IF_ROOM, ButtonType.ICON_AND_TEXT, IconPosition.END));
+            actions.add(TabSelectionEditorSelectionAction.createAction(
+                    sActivityTestRule.getActivity(), ShowMode.IF_ROOM, ButtonType.ICON_AND_TEXT,
+                    IconPosition.END, /*isIncognito=*/false));
 
             mTabSelectionEditorController.configureToolbarWithMenuItems(actions, null);
         });
@@ -1241,9 +1242,9 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             List<TabSelectionEditorAction> actions = new ArrayList<>();
-            actions.add(
-                    TabSelectionEditorSelectionAction.createAction(sActivityTestRule.getActivity(),
-                            ShowMode.MENU_ONLY, ButtonType.TEXT, IconPosition.START));
+            actions.add(TabSelectionEditorSelectionAction.createAction(
+                    sActivityTestRule.getActivity(), ShowMode.MENU_ONLY, ButtonType.TEXT,
+                    IconPosition.START, /*isIncognito=*/false));
             actions.add(TabSelectionEditorCloseAction.createAction(sActivityTestRule.getActivity(),
                     ShowMode.MENU_ONLY, ButtonType.TEXT, IconPosition.START));
 
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionActionUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionActionUnitTest.java
index 22cfa19d..9686777 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionActionUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorSelectionActionUnitTest.java
@@ -10,7 +10,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 
 import androidx.test.filters.SmallTest;
 
@@ -57,9 +56,10 @@
         MockitoAnnotations.initMocks(this);
         mContext = Robolectric.buildActivity(Activity.class).get();
         mContext.setTheme(org.chromium.chrome.tab_ui.R.style.Theme_BrowserUI_DayNight);
-        mAction = TabSelectionEditorSelectionAction.createAction(
-                mContext, ShowMode.IF_ROOM, ButtonType.ICON_AND_TEXT, IconPosition.END);
-        mTabModel = spy(new MockTabModel(false, null));
+        boolean isIncognito = false;
+        mAction = TabSelectionEditorSelectionAction.createAction(mContext, ShowMode.IF_ROOM,
+                ButtonType.ICON_AND_TEXT, IconPosition.END, isIncognito);
+        mTabModel = spy(new MockTabModel(isIncognito, null));
         when(mTabModelSelector.getCurrentModel()).thenReturn(mTabModel);
         // TODO(ckitagawa): Add tests for when this is true.
         mAction.configure(mTabModelSelector, mSelectionDelegate, mDelegate, false);
@@ -80,8 +80,10 @@
                         TabSelectionEditorActionProperties.CONTENT_DESCRIPTION_RESOURCE_ID));
         Assert.assertNotNull(
                 mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON));
-        Assert.assertNotNull(
+        Assert.assertNull(
                 mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON_TINT));
+        Assert.assertTrue(
+                mAction.getPropertyModel().get(TabSelectionEditorActionProperties.SKIP_ICON_TINT));
     }
 
     @Test
@@ -100,10 +102,6 @@
                 true, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ENABLED));
         Assert.assertEquals(
                 0, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ITEM_COUNT));
-        Assert.assertNotNull(
-                mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON));
-        Drawable selectAllIcon =
-                mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON);
 
         // 1 tab of 2 selected.
         selectedTabIds.add(0);
@@ -115,8 +113,6 @@
                 true, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ENABLED));
         Assert.assertEquals(
                 1, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ITEM_COUNT));
-        Assert.assertEquals(selectAllIcon,
-                mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON));
 
         // 2 tabs of 2 selected.
         selectedTabIds.add(1);
@@ -129,8 +125,6 @@
                 true, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ENABLED));
         Assert.assertEquals(
                 2, mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ITEM_COUNT));
-        Assert.assertNotEquals(selectAllIcon,
-                mAction.getPropertyModel().get(TabSelectionEditorActionProperties.ICON));
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorShareActionUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorShareActionUnitTest.java
index ef5b242..a35312b 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorShareActionUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorShareActionUnitTest.java
@@ -15,6 +15,7 @@
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -47,7 +48,6 @@
 import org.chromium.url.JUnitTestGURLs;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -74,15 +74,11 @@
     private MockTabModel mTabModel;
     private TabSelectionEditorShareAction mAction;
 
-    Map<Integer, GURL> mIdUrlMap = new HashMap<Integer, GURL>() {
-        {
-            put(1, JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_1));
-            put(2, JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_2));
-            put(3, JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_3));
-            put(4, JUnitTestGURLs.getGURL(JUnitTestGURLs.NTP_URL));
-            put(5, JUnitTestGURLs.getGURL(JUnitTestGURLs.ABOUT_BLANK));
-        }
-    };
+    Map<Integer, GURL> mIdUrlMap = Map.of(1, JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_1), 2,
+            JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_2), 3,
+            JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_3), 4,
+            JUnitTestGURLs.getGURL(JUnitTestGURLs.NTP_URL), 5,
+            JUnitTestGURLs.getGURL(JUnitTestGURLs.ABOUT_BLANK));
 
     @Before
     public void setUp() {
@@ -105,6 +101,11 @@
         mAction.configure(mTabModelSelector, mSelectionDelegate, mDelegate, false);
     }
 
+    @After
+    public void tearDown() {
+        TabSelectionEditorShareAction.setIntentCallbackForTesting(null);
+    }
+
     @Test
     @SmallTest
     public void testInherentActionProperties() {
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 d54374ff5..ea12d419 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -433,7 +433,7 @@
                 // LifecycleRegistry normally enforces it is called on the main thread, but this
                 // class will be preloaded in a background thread. The only method that gets called
                 // in the activity constructor is addObserver(), so just override that.
-                mLifecycleRegistry = new LifecycleRegistry(null) {
+                mLifecycleRegistry = new LifecycleRegistry(this) {
                     @Override
                     public void addObserver(LifecycleObserver observer) {}
                 };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index a27c047..3b3f27b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -134,8 +134,7 @@
     static final float BACKGROUND_TAB_BRIGHTNESS_DEFAULT = 1.f;
     static final float BACKGROUND_TAB_BRIGHTNESS_DIMMED = 0.65f;
     static final float DIVIDER_HIDDEN_OPACITY = 0.f;
-    static final float DIVIDER_DEFAULT_OPACITY = 0.2f;
-    static final float DIVIDER_BOLD_OPACITY = 0.6f;
+    static final float DIVIDER_DEFAULT_OPACITY = 1.f;
     static final float FADE_FULL_OPACITY_THRESHOLD_DP = 24.f;
 
     private static final int MESSAGE_RESIZE = 1;
@@ -765,25 +764,25 @@
      * a tab group when in edit mode.
      */
     private void updateDividers() {
-        if (!ChromeFeatureList.sTabStripRedesign.isEnabled() || mMultiStepTabCloseAnimRunning) {
-            return;
-        }
+        if (!ChromeFeatureList.sTabStripRedesign.isEnabled()) return;
+
+        // Validate the index. For example, the index can be {@link TabList.INVALID_TAB_INDEX} when
+        // all tabs are closed.
+        int index = mModel.index();
+        if (index < 0 || index >= mStripTabs.length) return;
 
         // Divider is never shown for the first tab.
         mStripTabs[0].setDividerOpacity(DIVIDER_HIDDEN_OPACITY);
 
-        int selectedTabId = mStripTabs[mModel.index()].getId();
+        int selectedTabId = mStripTabs[index].getId();
         for (int i = 1; i < mStripTabs.length; i++) {
             final StripLayoutTab prevTab = mStripTabs[i - 1];
             final StripLayoutTab currTab = mStripTabs[i];
-            if (prevTab.getTrailingMargin() > 0) {
-                // The first divider after a tab group margin is bolded to help indicate separation.
-                currTab.setDividerOpacity(DIVIDER_BOLD_OPACITY);
-            } else if (prevTab.getId() == selectedTabId || currTab.getId() == selectedTabId) {
+            if (prevTab.getId() == selectedTabId || currTab.getId() == selectedTabId) {
                 // Dividers adjacent to the selected tab are hidden.
                 currTab.setDividerOpacity(DIVIDER_HIDDEN_OPACITY);
             } else {
-                // Otherwise return the divider to the default opacity.
+                // All other dividers are visible.
                 currTab.setDividerOpacity(DIVIDER_DEFAULT_OPACITY);
             }
         }
@@ -1094,9 +1093,9 @@
 
         // 2. Set the dying state of the tab.
         tab.setIsDying(true);
-        Tab nextTab = mModel.getNextTabIfClosed(tab.getId(), /*uponExit=*/false);
 
         // 3. Setup animation end listener to resize and move tabs after tab closes.
+        Tab nextTab = mModel.getNextTabIfClosed(tab.getId(), /*uponExit=*/false);
         tabClosingAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -1255,6 +1254,9 @@
         AnimatorSet set = new AnimatorSet();
         set.playTogether(animationList);
         if (listener != null) set.addListener(listener);
+        if (mRunningAnimator != null && mRunningAnimator.isRunning()) {
+            mRunningAnimator.end();
+        }
         mRunningAnimator = set;
         mRunningAnimator.start();
     }
@@ -2086,15 +2088,13 @@
 
         // 4. We have hovered for the required time, so trigger a reorder.
         int direction = towardEnd ? 1 : -1;
-        int destinationTabId = mTabGroupModelFilter.getRootId(
-                getTabById(mStripTabs[curIndex + direction].getId()));
+        StripLayoutTab destTab = mStripTabs[curIndex + direction];
         float effectiveWidth = mCachedTabWidth - mTabOverlapWidth;
         float flipThreshold = effectiveWidth * REORDER_OVERLAP_SWITCH_PERCENTAGE;
         float minFlipOffset = mTabMarginWidth + flipThreshold;
         int numTabsToSkip =
                 1 + (int) Math.floor((Math.abs(offset) - minFlipOffset) / effectiveWidth);
-
-        mTabGroupModelFilter.mergeTabsToGroup(mInteractingTab.getId(), destinationTabId, true);
+        mTabGroupModelFilter.mergeTabsToGroup(mInteractingTab.getId(), destTab.getId(), true);
         RecordUserAction.record("MobileToolbarReorderTab.TabAddedToGroup");
 
         return towardEnd ? curIndex + 1 + numTabsToSkip : curIndex - numTabsToSkip;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
index 7b3c2f2..3ee0251 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
@@ -175,6 +175,8 @@
     // Divider Constants
     // TODO(crbug.com/1373632): Temp value until the 9-patches are updated.
     private static final int DIVIDER_OFFSET_X = 9;
+    @VisibleForTesting
+    static final float DIVIDER_FOLIO_LIGHT_OPACITY = 0.2f;
 
     private int mId = Tab.INVALID_TAB_ID;
 
@@ -419,7 +421,28 @@
      * @return The tint color resource for the tab divider.
      */
     public @ColorInt int getDividerTint() {
-        return SemanticColorUtils.getDefaultIconColorAccent1(mContext);
+        if (!ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+            // Dividers are only present in TSR. Return arbitrary color to avoid calculation.
+            return Color.TRANSPARENT;
+        }
+
+        if (mIncognito) {
+            return mContext.getColor(R.color.divider_line_bg_color_light);
+        }
+
+        if (TabUiFeatureUtilities.isTabStripFolioEnabled() && !ColorUtils.inNightMode(mContext)
+                && !mIncognito) {
+            // This color will not be used at full opacity. We can't set this using the alpha
+            // component of the {@code @ColorInt}, since it is ignored when loading resources
+            // with a specified tint in the CC layer (instead retaining the alpha of the original
+            // image). Instead, this is reflected by setting the opacity of the divider itself.
+            // See https://crbug.com/1373634.
+            return androidx.core.graphics.ColorUtils.setAlphaComponent(
+                    SemanticColorUtils.getDefaultIconColorAccent1(mContext),
+                    (int) (DIVIDER_FOLIO_LIGHT_OPACITY * 255));
+        }
+
+        return SemanticColorUtils.getDividerLineBgColor(mContext);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index bbbe82b..339fe479 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -632,16 +632,6 @@
     }
 
     @Test
-    @MediumTest
-    @Feature({"NewTabPage", "FeedNewTabPage", "RenderTest"})
-    @Features.EnableFeatures(ChromeFeatureList.FEED_ABLATION)
-    public void testRender_LoadNewTabPageWithDisabledFeed() throws IOException {
-        mRenderTestRule.render(
-                mActivityTestRule.getActivity().getActivityTab().getNativePage().getView(),
-                "feed_is_ablated");
-    }
-
-    @Test
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage"})
     public void testFeedReliabilityLoggingHideWithBack() throws IOException {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
index a7a998f..66a4eb0 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
@@ -135,6 +135,8 @@
         }
 
         TabUiFeatureUtilities.setTabMinWidthForTesting(null);
+        TabUiFeatureUtilities.setTabStripRedesignEnableFolioForTesting(false);
+        TabUiFeatureUtilities.setTabStripRedesignEnableDetachedForTesting(false);
     }
 
     /**
@@ -477,6 +479,8 @@
     @Test
     @Feature("Tab Strip Redesign")
     public void testUpdateDividers_WithTabSelected() {
+        TabUiFeatureUtilities.setTabStripRedesignEnableDetachedForTesting(true);
+
         // Setup with 5 tabs. Select tab 2.
         initializeTest(false, false, 2);
         StripLayoutTab[] tabs = mStripLayoutHelper.getStripLayoutTabs();
@@ -489,44 +493,15 @@
         float visibleOpacity = StripLayoutHelper.DIVIDER_DEFAULT_OPACITY;
         // clang-format off
         assertEquals("First divider should always be hidden.",
-                hiddenOpacity, tabs[0].getDividerOpacity(), EPSILON);
+            hiddenOpacity, tabs[0].getDividerOpacity(), EPSILON);
         assertEquals("Divider should be at default opacity.",
-                visibleOpacity, tabs[1].getDividerOpacity(), EPSILON);
+            visibleOpacity, tabs[1].getDividerOpacity(), EPSILON);
         assertEquals("Divider is adjacent to selected tab and should be hidden.",
-                hiddenOpacity, tabs[2].getDividerOpacity(), EPSILON);
+            hiddenOpacity, tabs[2].getDividerOpacity(), EPSILON);
         assertEquals("Divider is adjacent to selected tab and should be hidden.",
-                hiddenOpacity, tabs[3].getDividerOpacity(), EPSILON);
+            hiddenOpacity, tabs[3].getDividerOpacity(), EPSILON);
         assertEquals("Divider should be at default opacity.",
-                visibleOpacity, tabs[4].getDividerOpacity(), EPSILON);
-        // clang-format on
-    }
-
-    @Test
-    @Feature("Tab Strip Redesign")
-    public void testUpdateDividers_WithTabGroups() {
-        // Setup with 5 tabs. Select tab 4.
-        initializeTest(false, false, 4);
-        StripLayoutTab[] tabs = mStripLayoutHelper.getStripLayoutTabs();
-
-        // Trigger update to set divider values. Mock a tab group margin after tab 1.
-        tabs[1].setTrailingMargin(100.f);
-        mStripLayoutHelper.updateLayout(TIMESTAMP);
-
-        // Verify tab 2's divider is bolded.
-        float hiddenOpacity = StripLayoutHelper.DIVIDER_HIDDEN_OPACITY;
-        float visibleOpacity = StripLayoutHelper.DIVIDER_DEFAULT_OPACITY;
-        float boldedOpacity = StripLayoutHelper.DIVIDER_BOLD_OPACITY;
-        // clang-format off
-        assertEquals("First divider should always be hidden.",
-                hiddenOpacity, tabs[0].getDividerOpacity(), EPSILON);
-        assertEquals("Divider should be at default opacity.",
-                visibleOpacity, tabs[1].getDividerOpacity(), EPSILON);
-        assertEquals("Divider is after tab group margin and should be bolded.",
-                boldedOpacity, tabs[2].getDividerOpacity(), EPSILON);
-        assertEquals("Divider should be at default opacity.",
-                visibleOpacity, tabs[3].getDividerOpacity(), EPSILON);
-        assertEquals("Divider is adjacent to selected tab and should be hidden.",
-                hiddenOpacity, tabs[4].getDividerOpacity(), EPSILON);
+            visibleOpacity, tabs[4].getDividerOpacity(), EPSILON);
         // clang-format on
     }
 
@@ -1427,6 +1402,7 @@
         mStripLayoutHelper.onSizeChanged(SCREEN_WIDTH, SCREEN_HEIGHT, false, TIMESTAMP);
         StripLayoutTab[] tabs = mStripLayoutHelper.getStripLayoutTabs();
         StripLayoutTab thirdTab = tabs[2];
+        int oldSecondTabId = tabs[1].getId();
         groupTabs(0, 2);
 
         // Start reorder mode on third tab. Drag between tabs in group.
@@ -1452,9 +1428,9 @@
 
         // Verify interacting tab was merged into group at the second index.
         tabs = mStripLayoutHelper.getStripLayoutTabs();
-        // assertEquals("Third tab should now be second tab.", thirdTab, tabs[1]);
+        assertEquals("Third tab should now be second tab.", thirdTab, tabs[1]);
         verify(mTabGroupModelFilter)
-                .mergeTabsToGroup(eq(thirdTab.getId()), eq(tabs[0].getId()), eq(true));
+                .mergeTabsToGroup(eq(thirdTab.getId()), eq(oldSecondTabId), eq(true));
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTabTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTabTest.java
index 2498c5a5..870f82f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTabTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTabTest.java
@@ -30,6 +30,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.browser_ui.styles.ChromeColors;
+import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.ui.util.ColorUtils;
 
 /** Tests for {@link StripLayoutTab}. */
@@ -215,6 +216,58 @@
                 mIncognitoTab.getOutlineTint(false));
     }
 
+    @Test
+    @Feature("Tab Strip Redesign")
+    public void testGetDividerTint() {
+        int expectedColor = Color.TRANSPARENT;
+
+        // Normal.
+        assertEquals("Non-TSR tabs should not have a divider.", expectedColor,
+                mNormalTab.getDividerTint());
+
+        // Incognito.
+        assertEquals("Non-TSR tabs should not have a divider.", expectedColor,
+                mNormalTab.getDividerTint());
+    }
+
+    @Test
+    @Feature("Tab Strip Redesign")
+    @Features.EnableFeatures({ChromeFeatureList.TAB_STRIP_REDESIGN})
+    public void testGetDividerTint_TabStripRedesignFolio() {
+        TabUiFeatureUtilities.setTabStripRedesignEnableFolioForTesting(true);
+        int expectedColor;
+
+        // Normal.
+        expectedColor = androidx.core.graphics.ColorUtils.setAlphaComponent(
+                SemanticColorUtils.getDefaultIconColorAccent1(mContext),
+                (int) (StripLayoutTab.DIVIDER_FOLIO_LIGHT_OPACITY * 255));
+        assertEquals("TSR Folio light mode divider uses 20% icon color", expectedColor,
+                mNormalTab.getDividerTint());
+
+        // Incognito.
+        expectedColor = mContext.getColor(R.color.divider_line_bg_color_light);
+        assertEquals("TSR incognito dividers use the baseline color.", expectedColor,
+                mIncognitoTab.getDividerTint());
+    }
+
+    @Test
+    @Feature("Tab Strip Redesign")
+    @Features.EnableFeatures({ChromeFeatureList.TAB_STRIP_REDESIGN})
+    public void testGetDividerTint_TabStripRedesignDetached() {
+        TabUiFeatureUtilities.setTabStripRedesignEnableDetachedForTesting(true);
+        int expectedColor;
+
+        // Normal.
+        expectedColor = MaterialColors.getColor(mContext, R.attr.colorSurfaceVariant, TAG);
+        assertEquals("TSR detached divider uses surface variant.", expectedColor,
+                mNormalTab.getDividerTint());
+
+        // Incognito.
+        expectedColor = mContext.getColor(R.color.divider_line_bg_color_light);
+        assertEquals("TSR incognito dividers use the baseline color.", expectedColor,
+                mIncognitoTab.getDividerTint());
+    }
+
     private StripLayoutTab createStripLayoutTab(boolean incognito) {
         return new StripLayoutTab(mContext, 0, null, null, null, null, incognito);
     }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index f460c47..1208f04 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5028,18 +5028,6 @@
         <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_MULTIPLE_EXTENSIONS" desc="The tooltip text of the request access button that appears on the toolbar when an extension requests access to the site">
           Click to allow on <ph name="ORIGIN">$1<ex>google.com</ex></ph>:
         </message>
-        <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_SINGLE_EXTENSION_TITLE" desc="The title of the request access bubble to tell the user to accept the bubble to always run the extension on the site.">
-          Always allow "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" to run on <ph name="ORIGIN">$2<ex>google.com</ex></ph>?
-        </message>
-        <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_MULTIPLE_EXTENSIONS_TITLE" desc="The title of the request access bubble to tell the user to accept the bubble to always run the extensions on the site.">
-          Always allow these extensions to run on <ph name="ORIGIN">$1<ex>google.com</ex></ph>?
-        </message>
-        <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_OK_BUTTON_LABEL" desc="The label of the button to allow the extension(s) to run on the site.">
-          Always allow
-        </message>
-        <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_CANCEL_BUTTON_LABEL" desc="The label of the button to don't allow the extension(s) to run on the site.">
-          No thanks
-        </message>
         <message name="IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_TITLE_HAS_ACCESS" desc="The title in the card that shows up on mouse hover of a toolbar action when extension has access to the current site.">
           Allowed to read &amp; change
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_CANCEL_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_CANCEL_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 0ee9c50f..0000000
--- a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_CANCEL_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-736c2b36025d9d1c6b092c89c96c5d642286c7c8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1
deleted file mode 100644
index 1e6bb886..0000000
--- a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5e2995b256c98bd2bbfd964668465c864db058bb
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_OK_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_OK_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 0ee9c50f..0000000
--- a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_OK_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-736c2b36025d9d1c6b092c89c96c5d642286c7c8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_SINGLE_EXTENSION_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_SINGLE_EXTENSION_TITLE.png.sha1
deleted file mode 100644
index 0ee9c50f..0000000
--- a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_SINGLE_EXTENSION_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-736c2b36025d9d1c6b092c89c96c5d642286c7c8
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2c5015e..d47b606fc1 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -7989,9 +7989,6 @@
 grit("resources") {
   source = "browser_resources.grd"
 
-  # Required due to flattenhtml = "true" on a generated file.
-  enable_input_discovery_for_gn_analyze = false
-
   use_brotli = true
 
   defines = chrome_grit_defines
@@ -8013,11 +8010,11 @@
 
   deps = [
     ":chrome_internal_resources_gen",
-    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_webui_js",
+    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_js__generator",
     "//device/bluetooth/public/mojom:deprecated_experimental_interfaces_js",
     "//device/bluetooth/public/mojom:mojom_js",
-    "//services/device/public/mojom:usb_test_webui_js",
-    "//services/device/public/mojom:usb_webui_js",
+    "//services/device/public/mojom:usb_js__generator",
+    "//services/device/public/mojom:usb_test_js__generator",
     "//url/mojom:url_mojom_gurl_js",
     "//url/mojom:url_mojom_origin_js",
   ]
@@ -8046,6 +8043,7 @@
   }
   if (is_chromeos_ash) {
     deps += [
+      "//chrome/browser/ash/guest_os:guest_os_diagnostics_mojom_js__generator",
       "//chrome/browser/resources/chromeos/account_manager:css_wrapper_files",
       "//chrome/browser/resources/chromeos/account_manager:html_wrapper_files",
       "//chrome/browser/resources/chromeos/account_manager/components:html_wrapper_files",
@@ -8068,7 +8066,7 @@
       "//chrome/browser/ui/webui/ash/crostini_installer:mojo_bindings_js",
       "//chrome/browser/ui/webui/ash/crostini_upgrader:mojo_bindings_js",
       "//chrome/browser/ui/webui/ash/parent_access:mojo_bindings_js",
-      "//chrome/browser/ui/webui/ash/vm:mojo_bindings_webui_js",
+      "//chrome/browser/ui/webui/ash/vm:mojo_bindings_js__generator",
       "//chrome/browser/ui/webui/settings/ash:mojom_js",
       "//chrome/browser/ui/webui/settings/ash/os_apps_page/mojom:mojom_js",
       "//ui/webui/resources/cr_components/app_management:mojo_bindings_js",
@@ -8117,7 +8115,7 @@
     "//chrome/browser/resources/memory_internals:build_ts",
     "//chrome/browser/resources/predictors:build_ts",
     "//chrome/browser/ui/webui/internals/user_education:mojo_bindings_js",
-    "//components/site_engagement/core/mojom:mojo_bindings_webui_js",
+    "//components/site_engagement/core/mojom:mojo_bindings_js__generator",
   ]
 
   if (is_android || is_linux || is_chromeos || is_win) {
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index dc18063..11f7bf6 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4677,9 +4677,6 @@
     {"feed-stamp", flag_descriptions::kFeedStampName,
      flag_descriptions::kFeedStampDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(feed::kFeedStamp)},
-    {"feed-ablation", flag_descriptions::kFeedIsAblatedName,
-     flag_descriptions::kFeedIsAblatedDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(feed::kIsAblated)},
     {"feed-v2-hearts", flag_descriptions::kInterestFeedV2HeartsName,
      flag_descriptions::kInterestFeedV2HeartsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(feed::kInterestFeedV2Hearts)},
@@ -8971,6 +8968,9 @@
      kOsCrOS,
      FEATURE_VALUE_TYPE(
          chromeos::features::kEnableExternalKeyboardsInDiagnostics)},
+    {"enable-power-sounds", flag_descriptions::kSystemSoundsName,
+     flag_descriptions::kSystemSoundsDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kSystemSounds)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
     {"autofill-enforce-delays-in-strike-database",
diff --git a/chrome/browser/android/compositor/layer/tab_handle_layer.cc b/chrome/browser/android/compositor/layer/tab_handle_layer.cc
index da66404..df14cf6 100644
--- a/chrome/browser/android/compositor/layer/tab_handle_layer.cc
+++ b/chrome/browser/android/compositor/layer/tab_handle_layer.cc
@@ -52,10 +52,22 @@
     brightness_ = brightness;
     foreground_ = foreground;
     opacity_ = opacity;
-    cc::FilterOperations filters;
-    if (brightness_ != 1.0f && !foreground_)
-      filters.Append(cc::FilterOperation::CreateBrightnessFilter(brightness_));
-    layer_->SetFilters(filters);
+
+    // With the Tab Strip Redesign (TSR), inactive tabs no longer have a visible
+    // container. To achieve the same dimming effect, we need to set the opacity
+    // rather than adding a brightness filter. We can't swap to simply setting
+    // the opacity when TSR is disabled, because then, the tab containers can
+    // be seen overlapping. (See https://crbug.com/1373632).
+    if (base::FeatureList::IsEnabled(chrome::android::kTabStripRedesign)) {
+      tab_->SetOpacity(brightness_);
+    } else {
+      cc::FilterOperations filters;
+      if (brightness_ != 1.0f) {
+        filters.Append(
+            cc::FilterOperation::CreateBrightnessFilter(brightness_));
+      }
+      layer_->SetFilters(filters);
+    }
   }
 
   float original_x = x;
@@ -95,14 +107,14 @@
 
   if (title_layer) {
     title_layer->setOpacity(1.0f);
-    unsigned expected_children = 5;
+    unsigned expected_children = 4;
     title_layer_ = title_layer->layer();
-    if (layer_->children().size() < expected_children) {
-      layer_->AddChild(title_layer_);
-    } else if (layer_->children()[expected_children - 1]->id() !=
+    if (tab_->children().size() < expected_children) {
+      tab_->AddChild(title_layer_);
+    } else if (tab_->children()[expected_children - 1]->id() !=
                title_layer_->id()) {
-      layer_->ReplaceChild((layer_->children()[expected_children - 1]).get(),
-                           title_layer_);
+      tab_->ReplaceChild((tab_->children()[expected_children - 1]).get(),
+                         title_layer_);
     }
     title_layer->SetUIResourceIds();
   } else if (title_layer_.get()) {
@@ -209,6 +221,7 @@
 TabHandleLayer::TabHandleLayer(LayerTitleCache* layer_title_cache)
     : layer_title_cache_(layer_title_cache),
       layer_(cc::Layer::Create()),
+      tab_(cc::Layer::Create()),
       close_button_(cc::UIResourceLayer::Create()),
       divider_(cc::UIResourceLayer::Create()),
       decoration_tab_(cc::NinePatchLayer::Create()),
@@ -220,9 +233,14 @@
   if (!base::FeatureList::IsEnabled(chrome::android::kTabStripRedesign)) {
     tab_outline_->SetIsDrawable(true);
   }
-  layer_->AddChild(decoration_tab_);
-  layer_->AddChild(tab_outline_);
-  layer_->AddChild(close_button_);
+
+  tab_->AddChild(decoration_tab_);
+  tab_->AddChild(tab_outline_);
+  tab_->AddChild(close_button_);
+
+  // The divider is added as a separate child so its opacity can be controlled
+  // separately from the other tab items.
+  layer_->AddChild(tab_);
   layer_->AddChild(divider_);
 }
 
diff --git a/chrome/browser/android/compositor/layer/tab_handle_layer.h b/chrome/browser/android/compositor/layer/tab_handle_layer.h
index bd6e9e8d..6fde0e8 100644
--- a/chrome/browser/android/compositor/layer/tab_handle_layer.h
+++ b/chrome/browser/android/compositor/layer/tab_handle_layer.h
@@ -64,6 +64,7 @@
   raw_ptr<LayerTitleCache> layer_title_cache_;
 
   scoped_refptr<cc::Layer> layer_;
+  scoped_refptr<cc::Layer> tab_;
   scoped_refptr<cc::UIResourceLayer> close_button_;
   scoped_refptr<cc::UIResourceLayer> divider_;
   scoped_refptr<cc::NinePatchLayer> decoration_tab_;
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
index 3581aa6..af57558 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
@@ -343,7 +343,7 @@
       resource_manager->GetStaticResourceWithTint(close_resource_id,
                                                   close_tint);
   ui::Resource* divider_resource = resource_manager->GetStaticResourceWithTint(
-      divider_resource_id, divider_tint);
+      divider_resource_id, divider_tint, true);
   layer->SetProperties(id, close_button_resource, divider_resource,
                        tab_handle_resource, tab_handle_outline_resource,
                        foreground, close_pressed, toolbar_width, x, y, width,
diff --git a/chrome/browser/apps/platform_apps/shortcut_manager_factory.cc b/chrome/browser/apps/platform_apps/shortcut_manager_factory.cc
index 3c1aeac..2c044fb 100644
--- a/chrome/browser/apps/platform_apps/shortcut_manager_factory.cc
+++ b/chrome/browser/apps/platform_apps/shortcut_manager_factory.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut_manager.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
 
 // static
 AppShortcutManager* AppShortcutManagerFactory::GetForProfile(Profile* profile) {
@@ -30,8 +31,16 @@
 AppShortcutManagerFactory::~AppShortcutManagerFactory() {}
 
 KeyedService* AppShortcutManagerFactory::BuildServiceInstanceFor(
-    content::BrowserContext* profile) const {
-  return new AppShortcutManager(static_cast<Profile*>(profile));
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  if (!profile)
+    return nullptr;
+
+  // Do not instantiate the AppShortcutManager if web_apps are not supported.
+  if (!web_app::AreWebAppsEnabled(profile))
+    return nullptr;
+
+  return new AppShortcutManager(profile);
 }
 
 bool AppShortcutManagerFactory::ServiceIsCreatedWithBrowserContext() const {
diff --git a/chrome/browser/apps/platform_apps/shortcut_manager_factory.h b/chrome/browser/apps/platform_apps/shortcut_manager_factory.h
index 1e3fdb72..7732ab0 100644
--- a/chrome/browser/apps/platform_apps/shortcut_manager_factory.h
+++ b/chrome/browser/apps/platform_apps/shortcut_manager_factory.h
@@ -19,7 +19,7 @@
 // Singleton that owns all AppShortcutManagers and associates them with
 // Profiles. Listens for the Profile's destruction notification and cleans up
 // the associated AppShortcutManager.
-// AppShortcutManagers should not exist in incognito profiles.
+// AppShortcutManager should only exist for profiles where web apps are enabled.
 class AppShortcutManagerFactory : public ProfileKeyedServiceFactory {
  public:
   static AppShortcutManager* GetForProfile(Profile* profile);
@@ -34,7 +34,7 @@
 
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* profile) const override;
+      content::BrowserContext* context) const override;
   bool ServiceIsCreatedWithBrowserContext() const override;
   bool ServiceIsNULLWhileTesting() const override;
 };
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index cb072996..314e989ab 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -2376,6 +2376,7 @@
     "policy/reporting/metrics_reporting/audio/audio_events_observer.h",
     "policy/reporting/metrics_reporting/cros_healthd_metric_sampler.cc",
     "policy/reporting/metrics_reporting/cros_healthd_metric_sampler.h",
+    "policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_sampler_handler.h",
     "policy/reporting/metrics_reporting/cros_reporting_settings.cc",
     "policy/reporting/metrics_reporting/cros_reporting_settings.h",
     "policy/reporting/metrics_reporting/metric_reporting_manager.cc",
@@ -3049,6 +3050,8 @@
     "web_applications/shimless_rma_system_web_app_info.h",
     "web_applications/shortcut_customization_system_web_app_info.cc",
     "web_applications/shortcut_customization_system_web_app_info.h",
+    "web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.cc",
+    "web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.h",
     "web_applications/system_web_app_install_utils.cc",
     "web_applications/system_web_app_install_utils.h",
     "web_applications/terminal_source.cc",
@@ -3135,6 +3138,7 @@
     "//ash/webui/shimless_rma",
     "//ash/webui/shimless_rma/backend",
     "//ash/webui/shortcut_customization_ui",
+    "//ash/webui/shortcut_customization_ui/backend",
     "//base",
     "//base:i18n",
     "//build:chromeos_buildflags",
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.cc b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.cc
index f4850ff..ccdecfdf 100644
--- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.cc
+++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.cc
@@ -133,27 +133,11 @@
 
 ArcInputOverlayManager::~ArcInputOverlayManager() = default;
 
-void ArcInputOverlayManager::ReadData(const std::string& package_name,
-                                      aura::Window* top_level_window) {
-  auto touch_injector = std::make_unique<TouchInjector>(
-      top_level_window,
-      base::BindRepeating(&ArcInputOverlayManager::OnSaveProtoFile,
-                          weak_ptr_factory_.GetWeakPtr()));
-  loading_data_windows_.insert(top_level_window);
-
-  task_runner_->PostTaskAndReplyWithResult(
-      FROM_HERE,
-      base::BindOnce(&ArcInputOverlayManager::ReadDefaultData, Unretained(this),
-                     package_name, std::move(touch_injector)),
-      base::BindOnce(&ArcInputOverlayManager::OnFinishReadDefaultData,
-                     Unretained(this), package_name));
-}
-
 std::unique_ptr<TouchInjector> ArcInputOverlayManager::ReadDefaultData(
-    const std::string& package_name,
     std::unique_ptr<TouchInjector> touch_injector) {
   DCHECK(touch_injector);
 
+  const std::string& package_name = touch_injector->package_name();
   auto resource_id = GetInputOverlayResourceId(package_name);
   if (!resource_id)
     return touch_injector;
@@ -175,10 +159,13 @@
 }
 
 void ArcInputOverlayManager::OnFinishReadDefaultData(
-    const std::string& package_name,
     std::unique_ptr<TouchInjector> touch_injector) {
   DCHECK(touch_injector);
 
+  // Save |touch_injector->package_name()| first because
+  // |std::move(touch_injector)| is also called in the task runner.
+  std::string package_name = touch_injector->package_name();
+
   if (touch_injector->actions().empty()) {
     if (!beta_) {
       ResetForPendingTouchInjector(std::move(touch_injector));
@@ -200,6 +187,7 @@
       LOG(ERROR) << "GetTaskInfo method for ARC is not available";
       return;
     }
+
     VLOG(2) << "Fetch app category of package: " << package_name;
     app_instance->GetAppCategory(
         package_name,
@@ -236,7 +224,7 @@
 }
 
 std::unique_ptr<AppDataProto> ArcInputOverlayManager::GetProto(
-    const std::string& package_name) {
+    std::string package_name) {
   // |data_controller_| is null for test.
   return data_controller_ ? data_controller_->ReadProtoFromFile(package_name)
                           : nullptr;
@@ -271,7 +259,7 @@
 
 void ArcInputOverlayManager::OnSaveProtoFile(
     std::unique_ptr<AppDataProto> proto,
-    const std::string& package_name) {
+    std::string package_name) {
   task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&ArcInputOverlayManager::SaveFile, base::Unretained(this),
@@ -279,7 +267,7 @@
 }
 
 void ArcInputOverlayManager::SaveFile(std::unique_ptr<AppDataProto> proto,
-                                      const std::string& package_name) {
+                                      std::string package_name) {
   if (data_controller_)
     data_controller_->WriteProtoToFile(std::move(proto), package_name);
 }
@@ -412,7 +400,20 @@
       top_level_window->GetProperty(ash::kArcPackageNameKey);
   if (!package_name || package_name->empty())
     return;
-  ReadData(*package_name, top_level_window);
+
+  // Start to read data.
+  auto touch_injector = std::make_unique<TouchInjector>(
+      top_level_window, *package_name,
+      base::BindRepeating(&ArcInputOverlayManager::OnSaveProtoFile,
+                          weak_ptr_factory_.GetWeakPtr()));
+  loading_data_windows_.insert(top_level_window);
+
+  task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&ArcInputOverlayManager::ReadDefaultData, Unretained(this),
+                     std::move(touch_injector)),
+      base::BindOnce(&ArcInputOverlayManager::OnFinishReadDefaultData,
+                     Unretained(this)));
 }
 
 void ArcInputOverlayManager::OnWindowDestroying(aura::Window* window) {
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.h b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.h
index 6cef463..6cb20ce3 100644
--- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.h
+++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager.h
@@ -92,16 +92,11 @@
 
   class InputMethodObserver;
 
-  // Read all the data including both default data and customized data.
-  void ReadData(const std::string& package_name,
-                aura::Window* top_level_window);
   // Read default data.
   std::unique_ptr<TouchInjector> ReadDefaultData(
-      const std::string& package_name,
       std::unique_ptr<TouchInjector> touch_injector);
   // Called when finishing reading default data.
-  void OnFinishReadDefaultData(const std::string& package_name,
-                               std::unique_ptr<TouchInjector> touch_injector);
+  void OnFinishReadDefaultData(std::unique_ptr<TouchInjector> touch_injector);
   // Called when receiving app category from ARC.
   void OnReceiveAppCategory(std::unique_ptr<TouchInjector> touch_injector,
                             arc::mojom::AppCategory category);
@@ -110,15 +105,15 @@
   void ReadCustomizedData(const std::string& package_name,
                           std::unique_ptr<TouchInjector> touch_injector);
   // Get the Proto object from customized data.
-  std::unique_ptr<AppDataProto> GetProto(const std::string& package_name);
+  std::unique_ptr<AppDataProto> GetProto(std::string package_name);
   // Apply the customized proto data.
   void OnProtoDataAvailable(std::unique_ptr<TouchInjector> touch_injector,
                             std::unique_ptr<AppDataProto> proto);
   // Callback function triggered by Save button.
   void OnSaveProtoFile(std::unique_ptr<AppDataProto> proto,
-                       const std::string& package_name);
-  void SaveFile(std::unique_ptr<AppDataProto> proto,
-                const std::string& package_name);
+                       std::string package_name);
+  // Pass |package_name| by value because it runs on task runner.
+  void SaveFile(std::unique_ptr<AppDataProto> proto, std::string package_name);
   void NotifyTextInputState();
   void AddObserverToInputMethod();
   void RemoveObserverFromInputMethod();
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc
index b304830..7be3503 100644
--- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc
@@ -36,8 +36,7 @@
     "org.chromium.arc.testapp.inputoverlay_game";
 constexpr const float kTolerance = 0.999f;
 
-class MockDisplayOverlayController
-    : public input_overlay::DisplayOverlayController {
+class MockDisplayOverlayController : public DisplayOverlayController {
  public:
   explicit MockDisplayOverlayController(TouchInjector* touch_injector)
       : DisplayOverlayController(touch_injector, false) {}
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.cc b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.cc
index 242a897..f23ec44a 100644
--- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.cc
+++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.cc
@@ -13,7 +13,7 @@
 constexpr bool kCustomizationUsed = true;
 
 void InputOverlayUkm::RecordInputOverlayFeatureStateUkm(
-    const std::string& package_name,
+    std::string package_name,
     bool enable) {
   ukm::builders::GamingInputOverlay_Feature(
       ukm::AppSourceUrlRecorder::GetSourceIdForArcPackageName(package_name))
@@ -22,7 +22,7 @@
 }
 
 void InputOverlayUkm::RecordInputOverlayMappingHintStateUkm(
-    const std::string& package_name,
+    std::string package_name,
     bool enable) {
   ukm::builders::GamingInputOverlay_MappingHint(
       ukm::AppSourceUrlRecorder::GetSourceIdForArcPackageName(package_name))
@@ -31,7 +31,7 @@
 }
 
 void InputOverlayUkm::RecordInputOverlayCustomizedUsageUkm(
-    const std::string& package_name) {
+    std::string package_name) {
   ukm::builders::GamingInputOverlay_Customization(
       ukm::AppSourceUrlRecorder::GetSourceIdForArcPackageName(package_name))
       .SetCustomizationUsed(kCustomizationUsed)
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.h b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.h
index aef7ac91..67a3a51 100644
--- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.h
+++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_ukm.h
@@ -13,15 +13,13 @@
 // AppSourceUrlRecorder.
 class InputOverlayUkm {
  public:
-  static void RecordInputOverlayFeatureStateUkm(const std::string& package_name,
+  static void RecordInputOverlayFeatureStateUkm(std::string package_name,
                                                 bool enable);
 
-  static void RecordInputOverlayMappingHintStateUkm(
-      const std::string& package_name,
-      bool enable);
+  static void RecordInputOverlayMappingHintStateUkm(std::string package_name,
+                                                    bool enable);
 
-  static void RecordInputOverlayCustomizedUsageUkm(
-      const std::string& package_name);
+  static void RecordInputOverlayCustomizedUsageUkm(std::string package_name);
 };
 
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
index fd8eec5..abd8f8d 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -616,8 +616,8 @@
   touch_injector_->OnBindingRestore();
 }
 
-const std::string* DisplayOverlayController::GetPackageName() const {
-  return touch_injector_->GetPackageName();
+const std::string& DisplayOverlayController::GetPackageName() const {
+  return touch_injector_->package_name();
 }
 
 void DisplayOverlayController::OnApplyMenuState() {
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
index c8c5605..dbdb54b8 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -70,7 +70,7 @@
   // Restore back to original default binding when users press the restore
   // button after editing.
   void OnCustomizeRestore();
-  const std::string* GetPackageName() const;
+  const std::string& GetPackageName() const;
   // Once the menu state is loaded from protobuf data, it should be applied on
   // the view. For example, |InputMappingView| may not be visible if it is
   // hidden or input overlay is disabled.
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
index 007abae..80cfb4a 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"
 
+#include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "base/json/json_reader.h"
 #include "base/test/bind.h"
@@ -39,8 +40,9 @@
         "org.chromium.arc.testapp.inputoverlay");
     injector_ = std::make_unique<TouchInjector>(
         arc_test_window_->GetWindow(),
+        *arc_test_window_->GetWindow()->GetProperty(ash::kArcPackageNameKey),
         base::BindLambdaForTesting(
-            [&](std::unique_ptr<AppDataProto>, const std::string&) {}));
+            [&](std::unique_ptr<AppDataProto>, std::string) {}));
     controller_ =
         std::make_unique<DisplayOverlayController>(injector_.get(), false);
   }
diff --git a/chrome/browser/ash/arc/input_overlay/touch_injector.cc b/chrome/browser/ash/arc/input_overlay/touch_injector.cc
index 2589931..fd7b990 100644
--- a/chrome/browser/ash/arc/input_overlay/touch_injector.cc
+++ b/chrome/browser/ash/arc/input_overlay/touch_injector.cc
@@ -157,8 +157,10 @@
 };
 
 TouchInjector::TouchInjector(aura::Window* top_level_window,
+                             const std::string& package_name,
                              OnSaveProtoFileCallback save_file_callback)
     : window_(top_level_window),
+      package_name_(package_name),
       content_bounds_(CalculateWindowContentBounds(window_)),
       save_file_callback_(save_file_callback) {}
 
@@ -316,10 +318,6 @@
     action->RestoreToDefault();
 }
 
-const std::string* TouchInjector::GetPackageName() const {
-  return window_->GetProperty(ash::kArcPackageNameKey);
-}
-
 void TouchInjector::OnProtoDataAvailable(AppDataProto& proto) {
   LoadMenuEntryFromProto(proto);
   LoadMenuStateFromProto(proto);
@@ -347,21 +345,20 @@
 
 void TouchInjector::OnInputMenuViewRemoved() {
   OnSaveProtoFile();
-  const auto* package_name = GetPackageName();
   // Record UMA stats upon |InputMenuView| close because it needs to ignore the
   // unfinalized menu state change.
   if (touch_injector_enable_ != touch_injector_enable_uma_) {
     touch_injector_enable_uma_ = touch_injector_enable_;
     RecordInputOverlayFeatureState(touch_injector_enable_uma_);
     InputOverlayUkm::RecordInputOverlayFeatureStateUkm(
-        *package_name, touch_injector_enable_uma_);
+        package_name_, touch_injector_enable_uma_);
   }
 
   if (input_mapping_visible_ != input_mapping_visible_uma_) {
     input_mapping_visible_uma_ = input_mapping_visible_;
     RecordInputOverlayMappingHintState(input_mapping_visible_uma_);
     InputOverlayUkm::RecordInputOverlayMappingHintStateUkm(
-        *package_name, input_mapping_visible_uma_);
+        package_name_, input_mapping_visible_uma_);
   }
 }
 
@@ -771,8 +768,7 @@
 
 void TouchInjector::OnSaveProtoFile() {
   auto app_data_proto = ConvertToProto();
-  std::string package_name(*GetPackageName());
-  save_file_callback_.Run(std::move(app_data_proto), package_name);
+  save_file_callback_.Run(std::move(app_data_proto), package_name_);
 }
 
 void TouchInjector::AddMenuStateToProto(AppDataProto& proto) {
@@ -941,13 +937,12 @@
 void TouchInjector::RecordMenuStateOnLaunch() {
   touch_injector_enable_uma_ = touch_injector_enable_;
   input_mapping_visible_uma_ = input_mapping_visible_;
-  const auto* package_name = GetPackageName();
   RecordInputOverlayFeatureState(touch_injector_enable_uma_);
   InputOverlayUkm::RecordInputOverlayFeatureStateUkm(
-      *package_name, touch_injector_enable_uma_);
+      package_name_, touch_injector_enable_uma_);
   RecordInputOverlayMappingHintState(input_mapping_visible_uma_);
   InputOverlayUkm::RecordInputOverlayMappingHintStateUkm(
-      *package_name, input_mapping_visible_uma_);
+      package_name_, input_mapping_visible_uma_);
 }
 
 int TouchInjector::GetRewrittenTouchIdForTesting(ui::PointerId original_id) {
diff --git a/chrome/browser/ash/arc/input_overlay/touch_injector.h b/chrome/browser/ash/arc/input_overlay/touch_injector.h
index 00690f8e..98d6764c 100644
--- a/chrome/browser/ash/arc/input_overlay/touch_injector.h
+++ b/chrome/browser/ash/arc/input_overlay/touch_injector.h
@@ -48,51 +48,14 @@
 class TouchInjector : public ui::EventRewriter {
  public:
   using OnSaveProtoFileCallback =
-      base::RepeatingCallback<void(std::unique_ptr<AppDataProto>,
-                                   const std::string&)>;
+      base::RepeatingCallback<void(std::unique_ptr<AppDataProto>, std::string)>;
   TouchInjector(aura::Window* top_level_window,
+                const std::string& package_name,
                 OnSaveProtoFileCallback save_file_callback);
   TouchInjector(const TouchInjector&) = delete;
   TouchInjector& operator=(const TouchInjector&) = delete;
   ~TouchInjector() override;
 
-  aura::Window* window() { return window_; }
-  const gfx::RectF& content_bounds() const { return content_bounds_; }
-  const gfx::Transform* rotation_transform() {
-    return rotation_transform_.get();
-  }
-  const std::vector<std::unique_ptr<Action>>& actions() const {
-    return actions_;
-  }
-  bool is_mouse_locked() const { return is_mouse_locked_; }
-
-  bool touch_injector_enable() const { return touch_injector_enable_; }
-  void store_touch_injector_enable(bool enable) {
-    touch_injector_enable_ = enable;
-  }
-
-  bool input_mapping_visible() const { return input_mapping_visible_; }
-  void store_input_mapping_visible(bool enable) {
-    input_mapping_visible_ = enable;
-  }
-
-  bool first_launch() const { return first_launch_; }
-  void set_first_launch(bool first_launch) { first_launch_ = first_launch; }
-
-  bool show_nudge() const { return show_nudge_; }
-  void set_show_nudge(bool show_nudge) { show_nudge_ = show_nudge; }
-
-  void set_display_mode(DisplayMode mode) { display_mode_ = mode; }
-  void set_display_overlay_controller(DisplayOverlayController* controller) {
-    display_overlay_controller_ = controller;
-  }
-
-  bool enable_mouse_lock() { return enable_mouse_lock_; }
-  void set_enable_mouse_lock(bool enable) { enable_mouse_lock_ = true; }
-
-  bool beta() const { return beta_; }
-  void set_beta(bool beta) { beta_ = beta; }
-
   // Parse Json to actions.
   // Json value format:
   // {
@@ -130,7 +93,6 @@
   void OnBindingCancel();
   // Set input binding back to original binding.
   void OnBindingRestore();
-  const std::string* GetPackageName() const;
   void OnProtoDataAvailable(AppDataProto& proto);
   // Save the input menu state when the menu is closed.
   void OnInputMenuViewRemoved();
@@ -166,6 +128,44 @@
       const ui::Event& event,
       const Continuation continuation) override;
 
+  aura::Window* window() { return window_; }
+  const std::string& package_name() const { return package_name_; }
+  const gfx::RectF& content_bounds() const { return content_bounds_; }
+  const gfx::Transform* rotation_transform() {
+    return rotation_transform_.get();
+  }
+  const std::vector<std::unique_ptr<Action>>& actions() const {
+    return actions_;
+  }
+  bool is_mouse_locked() const { return is_mouse_locked_; }
+
+  bool touch_injector_enable() const { return touch_injector_enable_; }
+  void store_touch_injector_enable(bool enable) {
+    touch_injector_enable_ = enable;
+  }
+
+  bool input_mapping_visible() const { return input_mapping_visible_; }
+  void store_input_mapping_visible(bool enable) {
+    input_mapping_visible_ = enable;
+  }
+
+  bool first_launch() const { return first_launch_; }
+  void set_first_launch(bool first_launch) { first_launch_ = first_launch; }
+
+  bool show_nudge() const { return show_nudge_; }
+  void set_show_nudge(bool show_nudge) { show_nudge_ = show_nudge; }
+
+  void set_display_mode(DisplayMode mode) { display_mode_ = mode; }
+  void set_display_overlay_controller(DisplayOverlayController* controller) {
+    display_overlay_controller_ = controller;
+  }
+
+  bool enable_mouse_lock() { return enable_mouse_lock_; }
+  void set_enable_mouse_lock(bool enable) { enable_mouse_lock_ = true; }
+
+  bool beta() const { return beta_; }
+  void set_beta(bool beta) { beta_ = beta; }
+
  private:
   friend class ArcInputOverlayManagerTest;
   friend class TouchInjectorTest;
@@ -260,6 +260,7 @@
   // registered only when |window_| is focused. And TouchInjector doesn't own
   // |window_| and it is destroyed when |window_| is destroyed.
   raw_ptr<aura::Window> window_;
+  std::string package_name_;
   gfx::RectF content_bounds_;
   base::WeakPtr<ui::EventRewriterContinuation> continuation_;
   std::vector<std::unique_ptr<Action>> actions_;
diff --git a/chrome/browser/ash/arc/input_overlay/touch_injector_unittest.cc b/chrome/browser/ash/arc/input_overlay/touch_injector_unittest.cc
index be08802..4659396c 100644
--- a/chrome/browser/ash/arc/input_overlay/touch_injector_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/touch_injector_unittest.cc
@@ -326,8 +326,9 @@
                            .y();
     injector_ = std::make_unique<TouchInjector>(
         widget_->GetNativeWindow(),
+        *widget_->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey),
         base::BindLambdaForTesting(
-            [&](std::unique_ptr<AppDataProto>, const std::string&) {}));
+            [&](std::unique_ptr<AppDataProto>, std::string) {}));
     injector_->set_beta(true);
   }
 
@@ -907,8 +908,9 @@
   // Check whether AppDataProto is deserialized correctly.
   auto injector = std::make_unique<TouchInjector>(
       widget_->GetNativeWindow(),
+      *widget_->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey),
       base::BindLambdaForTesting(
-          [&](std::unique_ptr<AppDataProto>, const std::string&) {}));
+          [&](std::unique_ptr<AppDataProto>, std::string) {}));
   injector->set_beta(true);
   injector->ParseActions(*json_value);
   injector->OnProtoDataAvailable(*proto);
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/action_view_unittest.cc
index 3bcb072..a35bd52 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_view_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/arc/input_overlay/ui/action_view.h"
 
+#include "ash/public/cpp/window_properties.h"
 #include "base/json/json_reader.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ash/arc/input_overlay/constants.h"
@@ -129,8 +130,9 @@
     widget_ = CreateArcWindow(root_window(), gfx::Rect(200, 100, 400, 600));
     touch_injector_ = std::make_unique<TouchInjector>(
         widget_->GetNativeWindow(),
+        *widget_->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey),
         base::BindLambdaForTesting(
-            [&](std::unique_ptr<AppDataProto>, const std::string&) {}));
+            [&](std::unique_ptr<AppDataProto>, std::string) {}));
     touch_injector_->set_beta(true);
     touch_injector_->ParseActions(
         *base::JSONReader::ReadAndReturnValueWithError(
diff --git a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
index 12fd07a..1d76603 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
@@ -89,10 +89,11 @@
 constexpr char kBoardName[] = "entry.1492517074";
 constexpr char kOsVersion[] = "entry.1961594320";
 
-GURL GetAssembleUrl(DisplayOverlayController& controller) {
+// Pass |package_name| by value because the focus will be changed to the
+// browser.
+GURL GetAssembleUrl(std::string package_name) {
   GURL url(kFeedbackUrl);
-  const auto* package_name = controller.GetPackageName();
-  url = net::AppendQueryParameter(url, kGamePackageName, *package_name);
+  url = net::AppendQueryParameter(url, kGamePackageName, package_name);
   url = net::AppendQueryParameter(url, kBoardName,
                                   base::SysInfo::HardwareModelName());
   url = net::AppendQueryParameter(url, kOsVersion,
@@ -405,7 +406,7 @@
   }
   RecordInputOverlayCustomizedUsage();
   InputOverlayUkm::RecordInputOverlayCustomizedUsageUkm(
-      *(display_overlay_controller_->GetPackageName()));
+      display_overlay_controller_->GetPackageName());
   // Change display mode, load edit UI per action and overall edit buttons; make
   // sure the following line is at the bottom because edit mode will kill this
   // view.
@@ -417,7 +418,7 @@
   if (!display_overlay_controller_)
     return;
 
-  GURL url = GetAssembleUrl(*display_overlay_controller_);
+  GURL url = GetAssembleUrl(display_overlay_controller_->GetPackageName());
   ash::NewWindowDelegate::GetPrimary()->OpenUrl(
       url, ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
       ash::NewWindowDelegate::Disposition::kNewForegroundTab);
diff --git a/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view_unittest.cc
index bd8a62c0..ad635a7 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ash/arc/input_overlay/constants.h"
@@ -132,8 +133,9 @@
         "org.chromium.arc.testapp.inputoverlay");
     touch_injector_ = std::make_unique<TouchInjector>(
         arc_test_window_->GetWindow(),
+        *arc_test_window_->GetWindow()->GetProperty(ash::kArcPackageNameKey),
         base::BindLambdaForTesting(
-            [&](std::unique_ptr<AppDataProto>, const std::string&) {}));
+            [&](std::unique_ptr<AppDataProto>, std::string) {}));
     touch_injector_->set_beta(true);
     display_overlay_controller_ = std::make_unique<DisplayOverlayController>(
         touch_injector_.get(), /*first_launch=*/false);
diff --git a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
index 9155a65..5471e50 100644
--- a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
+++ b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
@@ -531,25 +531,23 @@
 
 absl::optional<base::Value> RecommendAppsFetcherImpl::ParseResponse(
     const base::Value& parsed_json) {
-  base::Value output(base::Value::Type::LIST);
+  base::Value::List output;
 
   // If the response is a dictionary, it is an error message in the
   // following format:
   //   {"Error code":"error code","Error message":"Error message"}
   if (parsed_json.is_dict()) {
-    const base::Value* response_error_code_value =
-        parsed_json.FindKeyOfType("Error code", base::Value::Type::STRING);
-    if (!response_error_code_value) {
+    const std::string* response_error_code_str =
+        parsed_json.FindStringKey("Error code");
+    if (!response_error_code_str) {
       LOG(ERROR) << "Unable to find error code";
       RecordUmaResponseParseResult(
           RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON);
       return absl::nullopt;
     }
 
-    base::StringPiece response_error_code_str =
-        response_error_code_value->GetString();
     int response_error_code = 0;
-    if (!base::StringToInt(response_error_code_str, &response_error_code)) {
+    if (!base::StringToInt(*response_error_code_str, &response_error_code)) {
       LOG(WARNING) << "Unable to parse error code: " << response_error_code_str;
       RecordUmaResponseParseResult(
           RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE);
@@ -579,24 +577,23 @@
   }
 
   for (auto& item : app_list) {
-    base::Value output_map(base::Value::Type::DICTIONARY);
-
     if (!item.is_dict()) {
       DVLOG(1) << "Cannot parse item.";
       continue;
     }
 
+    base::Value::Dict output_map;
     // Retrieve the app title.
     const base::Value* title =
         item.FindPathOfType({"title_", "name_"}, base::Value::Type::STRING);
     if (title)
-      output_map.SetKey("name", base::Value(title->GetString()));
+      output_map.Set("name", title->GetString());
 
     // Retrieve the package name.
     const base::Value* package_name =
         item.FindPathOfType({"id_", "id_"}, base::Value::Type::STRING);
     if (package_name)
-      output_map.SetKey("package_name", base::Value(package_name->GetString()));
+      output_map.Set("package_name", package_name->GetString());
 
     // Retrieve the icon URL for the app.
     //
@@ -610,9 +607,9 @@
         {"icon_", "url_", "privateDoNotAccessOrElseSafeUrlWrappedValue_"},
         base::Value::Type::STRING);
     if (icon_url)
-      output_map.SetKey("icon", base::Value(icon_url->GetString()));
+      output_map.Set("icon", icon_url->GetString());
 
-    if (output_map.DictEmpty()) {
+    if (output_map.empty()) {
       DVLOG(1) << "Invalid app item.";
       continue;
     }
@@ -621,9 +618,9 @@
   }
 
   RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_ERROR);
-  RecordUmaResponseAppCount(static_cast<int>(output.GetList().size()));
+  RecordUmaResponseAppCount(static_cast<int>(output.size()));
 
-  return output;
+  return base::Value(std::move(output));
 }
 
 void RecommendAppsFetcherImpl::OnJsonParsed(
diff --git a/chrome/browser/ash/login/screens/recommend_apps_screen_browsertest.cc b/chrome/browser/ash/login/screens/recommend_apps_screen_browsertest.cc
index 5203856..2962806 100644
--- a/chrome/browser/ash/login/screens/recommend_apps_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/recommend_apps_screen_browsertest.cc
@@ -86,23 +86,6 @@
   }
   ]})json";
 
-struct FakeAppInfo {
- public:
-  FakeAppInfo(const std::string& package_name, const std::string& name)
-      : package_name(package_name), name(name) {}
-  ~FakeAppInfo() = default;
-
-  base::Value ToValue() const {
-    base::Value result(base::Value::Type::DICTIONARY);
-    result.SetKey("package_name", base::Value(package_name));
-    result.SetKey("name", base::Value(name));
-    return result;
-  }
-
-  const std::string package_name;
-  const std::string name;
-};
-
 class StubRecommendAppsFetcher : public RecommendAppsFetcher {
  public:
   explicit StubRecommendAppsFetcher(RecommendAppsFetcherDelegate* delegate)
@@ -263,7 +246,7 @@
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetList(
           arc::prefs::kArcFastAppReinstallPackages);
 
-  base::Value expected_pref_value(base::Value::Type::LIST);
+  base::Value::List expected_pref_value;
   expected_pref_value.Append("test.app.foo.app1");
   expected_pref_value.Append("test.app.foo.app2");
   EXPECT_EQ(expected_pref_value, fast_reinstall_packages);
@@ -297,7 +280,7 @@
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetList(
           arc::prefs::kArcFastAppReinstallPackages);
 
-  base::Value expected_pref_value(base::Value::Type::LIST);
+  base::Value::List expected_pref_value;
   expected_pref_value.Append("test.app.foo.app2");
   EXPECT_EQ(expected_pref_value, fast_reinstall_packages);
 }
@@ -328,7 +311,7 @@
   const base::Value::List& fast_reinstall_packages =
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetList(
           arc::prefs::kArcFastAppReinstallPackages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), fast_reinstall_packages);
+  EXPECT_EQ(base::Value::List(), fast_reinstall_packages);
 }
 
 IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, SkipWithNoAppsSelected) {
@@ -360,15 +343,13 @@
   const base::Value::List& fast_reinstall_packages =
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetList(
           arc::prefs::kArcFastAppReinstallPackages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), fast_reinstall_packages);
+  EXPECT_EQ(base::Value::List(), fast_reinstall_packages);
 }
 
 IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest,
                        InstallWithNoAppsSelectedDisabled) {
   ShowScreenAndExpectLoadingStep();
 
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test.app.foo.app1", "Test app 1")};
   recommend_apps_fetcher_->SimulateSuccess();
 
   ExpectAppSelectionStep();
@@ -390,7 +371,7 @@
   const base::Value::List& fast_reinstall_packages =
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetList(
           arc::prefs::kArcFastAppReinstallPackages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), fast_reinstall_packages);
+  EXPECT_EQ(base::Value::List(), fast_reinstall_packages);
 }
 
 IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, ParseError) {
diff --git a/chrome/browser/ash/os_feedback/chrome_os_feedback_delegate_browsertest.cc b/chrome/browser/ash/os_feedback/chrome_os_feedback_delegate_browsertest.cc
index ffefd636..4855968 100644
--- a/chrome/browser/ash/os_feedback/chrome_os_feedback_delegate_browsertest.cc
+++ b/chrome/browser/ash/os_feedback/chrome_os_feedback_delegate_browsertest.cc
@@ -117,6 +117,9 @@
   void NotifyFeedbackDelayed() const override;
   feedback::FeedbackUploader* GetFeedbackUploaderForContext(
       content::BrowserContext* context) const override;
+  void OpenFeedback(
+      content::BrowserContext* context,
+      extensions::api::feedback_private::FeedbackSource source) const override;
 
  private:
   base::RepeatingCallback<void(bool)> on_fetch_completed_;
@@ -174,6 +177,10 @@
 
 void FakeFeedbackPrivateDelegate::NotifyFeedbackDelayed() const {}
 
+void FakeFeedbackPrivateDelegate::OpenFeedback(
+    content::BrowserContext* context,
+    extensions::api::feedback_private::FeedbackSource source) const {}
+
 feedback::FeedbackUploader*
 FakeFeedbackPrivateDelegate::GetFeedbackUploaderForContext(
     content::BrowserContext* context) const {
diff --git a/chrome/browser/ash/policy/reporting/app_install_event_log_manager_wrapper_unittest.cc b/chrome/browser/ash/policy/reporting/app_install_event_log_manager_wrapper_unittest.cc
index e6419a86..d37ddff6 100644
--- a/chrome/browser/ash/policy/reporting/app_install_event_log_manager_wrapper_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/app_install_event_log_manager_wrapper_unittest.cc
@@ -80,9 +80,10 @@
     event.set_event_type(em::AppInstallReportLogEvent::SUCCESS);
     log.Add(kPackageName, event);
     log.Store();
-    profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsRequested,
-                             app_list_);
-    profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsPending, app_list_);
+    profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsRequested,
+                                 app_list_.Clone());
+    profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsPending,
+                                 app_list_.Clone());
   }
 
   void FlushPendingTasks() {
@@ -133,7 +134,7 @@
   TestingProfile profile_;
 
   const base::FilePath log_file_path_;
-  base::ListValue app_list_;
+  base::Value::List app_list_;
 
   std::unique_ptr<AppInstallEventLogManagerWrapperTestable> wrapper_;
 
diff --git a/chrome/browser/ash/policy/reporting/arc_app_install_event_log_manager_unittest.cc b/chrome/browser/ash/policy/reporting/arc_app_install_event_log_manager_unittest.cc
index 60326b1..7ff8a2e11 100644
--- a/chrome/browser/ash/policy/reporting/arc_app_install_event_log_manager_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/arc_app_install_event_log_manager_unittest.cc
@@ -730,10 +730,12 @@
   log.Add(kPackageNames[0], event_);
   log.Store();
 
-  base::ListValue list;
+  base::Value::List list;
   list.Append("test");
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsRequested, list);
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsPending, list);
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsRequested,
+                               list.Clone());
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsPending,
+                               list.Clone());
 
   ArcAppInstallEventLogManager::Clear(&log_task_runner_wrapper_, &profile_);
   EXPECT_TRUE(profile_.GetPrefs()
@@ -764,10 +766,12 @@
   FlushNonDelayedTasks();
   VerifyLogFile();
 
-  base::ListValue list;
+  base::Value::List list;
   list.Append("test");
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsRequested, list);
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsPending, list);
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsRequested,
+                               list.Clone());
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsPending,
+                               list.Clone());
 
   ArcAppInstallEventLogManager::Clear(&log_task_runner_wrapper_, &profile_);
   EXPECT_TRUE(profile_.GetPrefs()
diff --git a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
index 98a7e6c..6599fb4 100644
--- a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
@@ -247,16 +247,16 @@
     PolicyMap policy_map;
 
     base::DictionaryValue arc_policy;
-    auto list = std::make_unique<base::ListValue>();
+    base::Value::List list;
 
     for (std::string package_name : package_names) {
       base::Value::Dict package;
       package.Set("installType", "FORCE_INSTALLED");
       package.Set("packageName", package_name);
-      list->Append(base::Value(std::move(package)));
+      list.Append(std::move(package));
     }
 
-    arc_policy.SetList("applications", std::move(list));
+    arc_policy.GetDict().Set("applications", std::move(list));
     std::string arc_policy_string;
     base::JSONWriter::Write(arc_policy, &arc_policy_string);
     SetPolicy(&policy_map, key::kArcEnabled, base::Value(true));
@@ -267,17 +267,17 @@
 
   base::DictionaryValue CreateComplianceReport(
       std::set<std::string> noncompliant_packages) {
-    auto details = std::make_unique<base::ListValue>();
+    base::Value::List details;
 
     for (std::string package_name : noncompliant_packages) {
       base::Value::Dict package;
       package.Set("nonComplianceReason", 5);
       package.Set("packageName", package_name);
-      details->Append(base::Value(std::move(package)));
+      details.Append(std::move(package));
     }
 
     base::DictionaryValue compliance_report;
-    compliance_report.SetList("nonComplianceDetails", std::move(details));
+    compliance_report.GetDict().Set("nonComplianceDetails", std::move(details));
     return compliance_report;
   }
 
@@ -299,10 +299,12 @@
 // pending. Clear all data related to app-install event log collection. Verify
 // that the lists are cleared.
 TEST_F(AppInstallEventLoggerTest, Clear) {
-  base::ListValue list;
+  base::Value::List list;
   list.Append("test");
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsRequested, list);
-  profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsPending, list);
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsRequested,
+                               list.Clone());
+  profile_.GetPrefs()->SetList(arc::prefs::kArcPushInstallAppsPending,
+                               list.Clone());
   ArcAppInstallEventLogger::Clear(&profile_);
   EXPECT_TRUE(profile_.GetPrefs()
                   ->FindPreference(arc::prefs::kArcPushInstallAppsRequested)
@@ -463,31 +465,31 @@
   PolicyMap new_policy_map;
 
   base::DictionaryValue arc_policy;
-  auto list = std::make_unique<base::ListValue>();
+  base::Value::List list;
 
   // Test that REQUIRED, PREINSTALLED and FORCE_INSTALLED are markers to include
   // app to the tracking. BLOCKED and AVAILABLE are excluded.
   base::Value::Dict package1;
   package1.Set("installType", "REQUIRED");
   package1.Set("packageName", kPackageName);
-  list->Append(base::Value(std::move(package1)));
+  list.Append(std::move(package1));
   base::Value::Dict package2;
   package2.Set("installType", "PREINSTALLED");
   package2.Set("packageName", kPackageName2);
-  list->Append(base::Value(std::move(package2)));
+  list.Append(std::move(package2));
   base::Value::Dict package3;
   package3.Set("installType", "FORCE_INSTALLED");
   package3.Set("packageName", kPackageName3);
-  list->Append(base::Value(std::move(package3)));
+  list.Append(std::move(package3));
   base::Value::Dict package4;
   package4.Set("installType", "BLOCKED");
   package4.Set("packageName", kPackageName4);
-  list->Append(base::Value(std::move(package4)));
+  list.Append(std::move(package4));
   base::Value::Dict package5;
   package5.Set("installType", "AVAILABLE");
   package5.Set("packageName", kPackageName5);
-  list->Append(base::Value(std::move(package5)));
-  arc_policy.SetList("applications", std::move(list));
+  list.Append(std::move(package5));
+  arc_policy.GetDict().Set("applications", std::move(list));
 
   std::string arc_policy_string;
   base::JSONWriter::Write(arc_policy, &arc_policy_string);
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_sampler_handler.h b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_sampler_handler.h
new file mode 100644
index 0000000..aae51f25
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_sampler_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_CROS_HEALTHD_SAMPLER_HANDLERS_CROS_HEALTHD_SAMPLER_HANDLER_H_
+#define CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_CROS_HEALTHD_SAMPLER_HANDLERS_CROS_HEALTHD_SAMPLER_HANDLER_H_
+
+#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
+#include "components/reporting/metrics/sampler.h"
+
+namespace reporting {
+
+namespace cros_healthd = ::ash::cros_healthd::mojom;
+
+// CrosHealthdSamplerHandler is an interface that can be used to process the
+// returned result after probing the croshealthd for a particular category.
+class CrosHealthdSamplerHandler {
+ public:
+  virtual ~CrosHealthdSamplerHandler() = default;
+
+  // Converts |result| to MetricData and passes it to |callback|. This method is
+  // used when there's only one possible MetricType for the metric category.
+  virtual void HandleResult(cros_healthd::TelemetryInfoPtr result,
+                            OptionalMetricCallback callback) const = 0;
+};
+
+}  // namespace reporting
+
+#endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_CROS_HEALTHD_SAMPLER_HANDLERS_CROS_HEALTHD_SAMPLER_HANDLER_H_
diff --git a/chrome/browser/ash/web_applications/shortcut_customization_ui/OWNERS b/chrome/browser/ash/web_applications/shortcut_customization_ui/OWNERS
new file mode 100644
index 0000000..d8674605
--- /dev/null
+++ b/chrome/browser/ash/web_applications/shortcut_customization_ui/OWNERS
@@ -0,0 +1 @@
+file://ash/webui/shortcut_customization_ui/OWNERS
\ No newline at end of file
diff --git a/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.cc b/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.cc
new file mode 100644
index 0000000..e5a7b02
--- /dev/null
+++ b/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.cc
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors
+// 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/shortcut_customization_ui/chrome_shortcut_customization_delegate.h"
+#include "chrome/browser/profiles/profile.h"
+
+ChromeShortcutCustomizationDelegate::ChromeShortcutCustomizationDelegate(
+    content::WebUI* web_ui)
+    : web_ui_(web_ui) {}
+
+ChromeShortcutCustomizationDelegate::~ChromeShortcutCustomizationDelegate() =
+    default;
+
+PrefService* ChromeShortcutCustomizationDelegate::GetPrefService() {
+  return Profile::FromWebUI(web_ui_)->GetPrefs();
+}
diff --git a/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.h b/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.h
new file mode 100644
index 0000000..3e36a8c
--- /dev/null
+++ b/chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.h
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors
+// 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_SHORTCUT_CUSTOMIZATION_UI_CHROME_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
+#define CHROME_BROWSER_ASH_WEB_APPLICATIONS_SHORTCUT_CUSTOMIZATION_UI_CHROME_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
+
+#include "ash/webui/shortcut_customization_ui/backend/shortcut_customization_delegate.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_ui.h"
+
+/**
+ * Implementation of the ShortcutCustomizationDelegate interface. Provides the
+ * shortcut customization app code in ash/ with functions that only exist in
+ * chrome/.
+ */
+class ChromeShortcutCustomizationDelegate
+    : public ash::shortcut_ui::ShortcutCustomizationDelegate {
+ public:
+  explicit ChromeShortcutCustomizationDelegate(content::WebUI* web_ui);
+
+  ChromeShortcutCustomizationDelegate(
+      const ChromeShortcutCustomizationDelegate&) = delete;
+  ChromeShortcutCustomizationDelegate& operator=(
+      const ChromeShortcutCustomizationDelegate&) = delete;
+  ~ChromeShortcutCustomizationDelegate() override;
+
+  // Get the pref service.
+  PrefService* GetPrefService() override;
+
+ private:
+  content::WebUI* web_ui_;
+};
+
+#endif  // CHROME_BROWSER_ASH_WEB_APPLICATIONS_SHORTCUT_CUSTOMIZATION_UI_CHROME_SHORTCUT_CUSTOMIZATION_DELEGATE_H_
diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc
index 0e52a00..0d66294 100644
--- a/chrome/browser/browser_features.cc
+++ b/chrome/browser/browser_features.cc
@@ -153,7 +153,7 @@
 // Motivation:
 //  The blue border behavior used to cause problems on ChromeOS - see
 //  crbug.com/1320262 for Ash (fixed) and crbug.com/1030925 for Lacros
-//  (relatively old bug -- we would like to observe whether it's still
+//  (relatively old bug - we would like to observe whether it's still
 //  there). This flag is introduced as means of disabling this feature in case
 //  of possible future regressions.
 //
diff --git a/chrome/browser/creator/android/java/res/values/styles.xml b/chrome/browser/creator/android/java/res/values/styles.xml
index 8287a157..d20cad6d 100644
--- a/chrome/browser/creator/android/java/res/values/styles.xml
+++ b/chrome/browser/creator/android/java/res/values/styles.xml
@@ -29,7 +29,7 @@
         <item name="android:textAppearance">@style/TextAppearance.Button.Text.Blue</item>
         <item name="buttonTextColor">@macro/default_text_color_secondary</item>
         <item name="buttonColor">@android:color/white</item>
-        <item name="rippleColor">@color/text_button_ripple_color</item>
+        <item name="rippleColor">@color/text_button_ripple_color_list</item>
         <item name="buttonRaised">true</item>
     </style>
 </resources>
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
index b4bbb10..d5b87a0 100644
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
@@ -536,20 +536,18 @@
       "  \"priority\": 300                                               \n"
       "}                                                                 ";
 
-  std::unique_ptr<base::Value> value1 =
-      base::JSONReader::ReadDeprecated(kRule1);
-  ASSERT_TRUE(value1.get());
-  std::unique_ptr<base::Value> value2 =
-      base::JSONReader::ReadDeprecated(kRule2);
-  ASSERT_TRUE(value2.get());
+  absl::optional<base::Value> value1 = base::JSONReader::Read(kRule1);
+  ASSERT_TRUE(value1);
+  absl::optional<base::Value> value2 = base::JSONReader::Read(kRule2);
+  ASSERT_TRUE(value2);
 
   std::vector<const api::events::Rule*> rules;
   api::events::Rule rule1;
   api::events::Rule rule2;
   rules.push_back(&rule1);
   rules.push_back(&rule2);
-  ASSERT_TRUE(api::events::Rule::Populate(*value1, &rule1));
-  ASSERT_TRUE(api::events::Rule::Populate(*value2, &rule2));
+  ASSERT_TRUE(api::events::Rule::Populate(value1.value(), &rule1));
+  ASSERT_TRUE(api::events::Rule::Populate(value2.value(), &rule2));
 
   scoped_refptr<WebRequestRulesRegistry> registry(
       new TestWebRequestRulesRegistry(&profile_));
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
index 0751b03..10082140 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Disable everything on windows only. http://crbug.com/306144
 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
 
 #include <stddef.h>
@@ -4817,8 +4816,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest,
-                       DownloadExtensionBubbleEnabledTest_SetUiOptions) {
+IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest, SetUiOptions) {
   DownloadManager::DownloadVector items;
   CreateTwoDownloads(&items);
   ScopedItemVectorCanceller delete_items(&items);
@@ -4837,9 +4835,8 @@
   EXPECT_FALSE(GetDownloadToolbarButton()->IsShowing());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    DownloadExtensionBubbleEnabledTest,
-    DownloadExtensionBubbleEnabledTest_SetUiOptionsBeforeDownloadStart) {
+IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest,
+                       SetUiOptionsBeforeDownloadStart) {
   LoadExtension("downloads_split");
   EXPECT_TRUE(RunFunction(base::MakeRefCounted<DownloadsSetUiOptionsFunction>(),
                           R"([{"enabled": false}])"));
@@ -4850,9 +4847,8 @@
 }
 
 // Flaky. crbug.com/1386043
-IN_PROC_BROWSER_TEST_F(
-    DownloadExtensionBubbleEnabledTest,
-    DISABLED_DownloadExtensionBubbleEnabledTest_SetUiOptionsShowDetails) {
+IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest,
+                       DISABLED_SetUiOptionsShowDetails) {
   LoadExtension("downloads_split");
   DownloadManager::DownloadVector items;
   CreateFirstSlowTestDownload();
@@ -4873,9 +4869,8 @@
   EXPECT_TRUE(GetDownloadToolbarButton()->IsShowingDetails());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    DownloadExtensionBubbleEnabledTest,
-    DownloadExtensionBubbleEnabledTest_SetUiOptionsOffTheRecord) {
+IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest,
+                       SetUiOptionsOffTheRecord) {
   LoadExtension("downloads_split");
   EXPECT_TRUE(RunFunction(base::MakeRefCounted<DownloadsSetUiOptionsFunction>(),
                           R"([{"enabled": false}])"));
@@ -4896,9 +4891,8 @@
   EXPECT_TRUE(GetDownloadToolbarButton()->IsShowing());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    DownloadExtensionBubbleEnabledTest,
-    DownloadExtensionBubbleEnabledTest_SetUiOptionsMultipleExtensions) {
+IN_PROC_BROWSER_TEST_F(DownloadExtensionBubbleEnabledTest,
+                       SetUiOptionsMultipleExtensions) {
   LoadExtension("downloads_split");
   EXPECT_TRUE(RunFunction(base::MakeRefCounted<DownloadsSetUiOptionsFunction>(),
                           R"([{"enabled": false}])"));
diff --git a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc
index 3ec1b84..73aa3c6 100644
--- a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc
+++ b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/feedback/system_logs/chrome_system_logs_fetcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/simple_message_box.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/feedback/system_logs/system_logs_fetcher.h"
@@ -269,4 +270,21 @@
   return feedback::FeedbackUploaderFactoryChrome::GetForBrowserContext(context);
 }
 
+void ChromeFeedbackPrivateDelegate::OpenFeedback(
+    content::BrowserContext* context,
+    api::feedback_private::FeedbackSource source) const {
+  GURL url;
+
+  DCHECK(source ==
+         api::feedback_private::FeedbackSource::FEEDBACK_SOURCE_QUICKOFFICE);
+
+  Profile* profile = Profile::FromBrowserContext(context);
+  chrome::ShowFeedbackPage(url, profile,
+                           /*source=*/chrome::kFeedbackSourceQuickOffice,
+                           /*description_template=*/std::string(),
+                           /*description_placeholder_text=*/std::string(),
+                           /*category_tag=*/std::string(),
+                           /*extra_diagnostics=*/std::string());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.h b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.h
index 33b7cbad..20d472da 100644
--- a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.h
+++ b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.h
@@ -42,6 +42,9 @@
   void NotifyFeedbackDelayed() const override;
   feedback::FeedbackUploader* GetFeedbackUploaderForContext(
       content::BrowserContext* context) const override;
+  void OpenFeedback(
+      content::BrowserContext* context,
+      api::feedback_private::FeedbackSource source) const override;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/permissions/permissions_api_helpers.cc b/chrome/browser/extensions/api/permissions/permissions_api_helpers.cc
index 16c9cef..fc857cb 100644
--- a/chrome/browser/extensions/api/permissions/permissions_api_helpers.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_api_helpers.cc
@@ -46,9 +46,9 @@
     base::StringPiece permission_arg,
     const std::string& permission_str,
     std::string* error) {
-  std::unique_ptr<base::Value> permission_json =
-      base::JSONReader::ReadDeprecated(permission_arg);
-  if (!permission_json.get()) {
+  absl::optional<base::Value> permission_json =
+      base::JSONReader::Read(permission_arg);
+  if (!permission_json) {
     *error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
     return nullptr;
   }
@@ -69,7 +69,7 @@
   }
 
   CHECK(permission);
-  if (!permission->FromValue(permission_json.get(), nullptr, nullptr)) {
+  if (!permission->FromValue(&permission_json.value(), nullptr, nullptr)) {
     *error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
     return nullptr;
   }
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
index c226a376..ed798bc 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
@@ -204,7 +204,7 @@
     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         base::BindOnce(&extensions::ExtensionService::TerminateExtension,
-                       service->AsWeakPtr(), extension_id));
+                       service->AsExtensionServiceWeakPtr(), extension_id));
     extensions::WarningSet warnings;
     warnings.insert(
         extensions::Warning::CreateReloadTooFrequentWarning(
@@ -220,7 +220,7 @@
     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         base::BindOnce(&extensions::ExtensionService::ReloadExtension,
-                       service->AsWeakPtr(), extension_id));
+                       service->AsExtensionServiceWeakPtr(), extension_id));
   }
 }
 
diff --git a/chrome/browser/extensions/chrome_app_api_browsertest.cc b/chrome/browser/extensions/chrome_app_api_browsertest.cc
index 031cc85c..ca9eb938 100644
--- a/chrome/browser/extensions/chrome_app_api_browsertest.cc
+++ b/chrome/browser/extensions/chrome_app_api_browsertest.cc
@@ -152,13 +152,13 @@
           browser()->tab_strip_model()->GetActiveWebContents(),
           kGetAppDetails,
           &result));
-  std::unique_ptr<base::DictionaryValue> app_details(
-      static_cast<base::DictionaryValue*>(
-          base::JSONReader::ReadDeprecated(result).release()));
+  absl::optional<base::Value> result_value = base::JSONReader::Read(result);
+  ASSERT_TRUE(result_value);
+  base::Value app_details(std::move(*result_value));
+
   // extension->manifest() does not contain the id.
-  app_details->RemoveKey("id");
-  EXPECT_TRUE(app_details.get());
-  EXPECT_EQ(*app_details, *extension->manifest()->value());
+  app_details.RemoveKey("id");
+  EXPECT_EQ(app_details, *extension->manifest()->value());
 
   // Try to change app.isInstalled.  Should silently fail, so
   // that isInstalled should have the initial value.
diff --git a/chrome/browser/extensions/chrome_zipfile_installer.cc b/chrome/browser/extensions/chrome_zipfile_installer.cc
index aed1524..83eacb2e 100644
--- a/chrome/browser/extensions/chrome_zipfile_installer.cc
+++ b/chrome/browser/extensions/chrome_zipfile_installer.cc
@@ -35,7 +35,7 @@
             zip_file, error, extension_service_weak->profile(),
             /*noisy_on_failure=*/true);
       },
-      service->AsWeakPtr());
+      service->AsExtensionServiceWeakPtr());
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index efd1c64..fb1bb2b2 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -84,7 +84,7 @@
 // static
 scoped_refptr<CrxInstaller> CrxInstaller::CreateSilent(
     ExtensionService* frontend) {
-  return new CrxInstaller(frontend->AsWeakPtr(),
+  return new CrxInstaller(frontend->AsExtensionServiceWeakPtr(),
                           std::unique_ptr<ExtensionInstallPrompt>(), nullptr);
 }
 
@@ -92,7 +92,8 @@
 scoped_refptr<CrxInstaller> CrxInstaller::Create(
     ExtensionService* frontend,
     std::unique_ptr<ExtensionInstallPrompt> client) {
-  return new CrxInstaller(frontend->AsWeakPtr(), std::move(client), nullptr);
+  return new CrxInstaller(frontend->AsExtensionServiceWeakPtr(),
+                          std::move(client), nullptr);
 }
 
 // static
@@ -100,7 +101,8 @@
     ExtensionService* service,
     std::unique_ptr<ExtensionInstallPrompt> client,
     const WebstoreInstaller::Approval* approval) {
-  return new CrxInstaller(service->AsWeakPtr(), std::move(client), approval);
+  return new CrxInstaller(service->AsExtensionServiceWeakPtr(),
+                          std::move(client), approval);
 }
 
 CrxInstaller::CrxInstaller(base::WeakPtr<ExtensionService> service_weak,
diff --git a/chrome/browser/extensions/data_deleter.cc b/chrome/browser/extensions/data_deleter.cc
index ea2ff96..f362491 100644
--- a/chrome/browser/extensions/data_deleter.cc
+++ b/chrome/browser/extensions/data_deleter.cc
@@ -139,9 +139,10 @@
   if (has_isolated_storage) {
     profile->AsyncObliterateStoragePartition(
         util::GetPartitionDomainForExtension(extension),
-        base::BindOnce(
-            &OnNeedsToGarbageCollectIsolatedStorage,
-            ExtensionSystem::Get(profile)->extension_service()->AsWeakPtr()),
+        base::BindOnce(&OnNeedsToGarbageCollectIsolatedStorage,
+                       ExtensionSystem::Get(profile)
+                           ->extension_service()
+                           ->AsExtensionServiceWeakPtr()),
         subtask_done_callback);
   }
   if (delete_extension_origin) {
diff --git a/chrome/browser/extensions/extension_disabled_ui.cc b/chrome/browser/extensions/extension_disabled_ui.cc
index 4ead87e..790a9db 100644
--- a/chrome/browser/extensions/extension_disabled_ui.cc
+++ b/chrome/browser/extensions/extension_disabled_ui.cc
@@ -235,7 +235,8 @@
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(&ExtensionService::GrantPermissionsAndEnableExtension,
-                     service_->AsWeakPtr(), base::RetainedRef(extension_)));
+                     service_->AsExtensionServiceWeakPtr(),
+                     base::RetainedRef(extension_)));
 }
 
 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index cd8fa1d3..8826dd1 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -447,6 +447,10 @@
   return &corrupted_extension_reinstaller_;
 }
 
+base::WeakPtr<ExtensionServiceInterface> ExtensionService::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 ExtensionService::~ExtensionService() {
   UpgradeDetector::GetInstance()->RemoveObserver(this);
   // No need to unload extensions here because they are profile-scoped, and the
@@ -727,8 +731,9 @@
         UnpackedInstaller::Create(this);
     unpacked_installer->set_be_noisy_on_failure(load_error_behavior ==
                                                 LoadErrorBehavior::kNoisy);
-    unpacked_installer->set_completion_callback(base::BindOnce(
-        &ExtensionService::OnUnpackedReloadFailure, AsWeakPtr()));
+    unpacked_installer->set_completion_callback(
+        base::BindOnce(&ExtensionService::OnUnpackedReloadFailure,
+                       AsExtensionServiceWeakPtr()));
     unpacked_installer->Load(path);
   }
 }
@@ -1927,7 +1932,7 @@
   scoped_refptr<CrxInstaller> installer(CrxInstaller::CreateSilent(this));
   installer->AddInstallerCallback(
       base::BindOnce(&ExtensionService::InstallationFromExternalFileFinished,
-                     AsWeakPtr(), info.extension_id));
+                     AsExtensionServiceWeakPtr(), info.extension_id));
   installer->set_install_source(info.crx_location);
   installer->set_expected_id(info.extension_id);
   installer->set_expected_version(info.version,
@@ -1984,7 +1989,8 @@
   // access to the Extension and ExtensionHost.
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(&ExtensionService::TerminateExtension,
-                                AsWeakPtr(), extension_host->extension_id()));
+                                AsExtensionServiceWeakPtr(),
+                                extension_host->extension_id()));
 }
 
 void ExtensionService::OnAppTerminating() {
@@ -2035,7 +2041,8 @@
             base::BindOnce(
                 base::IgnoreResult(
                     &ExtensionService::FinishDelayedInstallationIfReady),
-                AsWeakPtr(), extension_id, false /*install_immediately*/),
+                AsExtensionServiceWeakPtr(), extension_id,
+                false /*install_immediately*/),
             kUpdateIdleDelay);
       }
     }
@@ -2136,7 +2143,8 @@
 void ExtensionService::OnBlocklistUpdated() {
   blocklist_->GetBlocklistedIDs(
       registry_->GenerateInstalledExtensionsSet()->GetIDs(),
-      base::BindOnce(&ExtensionService::ManageBlocklist, AsWeakPtr()));
+      base::BindOnce(&ExtensionService::ManageBlocklist,
+                     AsExtensionServiceWeakPtr()));
 }
 
 void ExtensionService::OnUpgradeRecommended() {
@@ -2281,8 +2289,8 @@
 
   blocklist_->IsDatabaseReady(base::BindOnce(
       [](base::WeakPtr<ExtensionService> service, bool is_ready) {
-      DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-      if (!service || !is_ready) {
+        DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+        if (!service || !is_ready) {
           // Either the service was torn down or the database isn't
           // ready yet (and is effectively empty). Either way, no need
           // to update the blocklisted extensions.
@@ -2290,7 +2298,7 @@
         }
         service->OnBlocklistUpdated();
       },
-      AsWeakPtr()));
+      AsExtensionServiceWeakPtr()));
 }
 
 void ExtensionService::UninstallMigratedExtensions() {
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index b55a1fd..ccbf89e7 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -86,8 +86,7 @@
 
 // This is an interface class to encapsulate the dependencies that
 // various classes have on ExtensionService. This allows easy mocking.
-class ExtensionServiceInterface
-    : public base::SupportsWeakPtr<ExtensionServiceInterface> {
+class ExtensionServiceInterface {
  public:
   virtual ~ExtensionServiceInterface() {}
 
@@ -162,6 +161,8 @@
   // This will trigger an update/reinstall of the extensions saved in the
   // provider's prefs.
   virtual void ReinstallProviderExtensions() = 0;
+
+  virtual base::WeakPtr<ExtensionServiceInterface> AsWeakPtr() = 0;
 };
 
 // Manages installed and running Chromium extensions. An instance is shared
@@ -212,6 +213,7 @@
   void CheckManagementPolicy() override;
   void CheckForUpdatesSoon() override;
   void ReinstallProviderExtensions() override;
+  base::WeakPtr<ExtensionServiceInterface> AsWeakPtr() override;
 
   // ExternalProvider::VisitorInterface implementation.
   // Exposed for testing.
@@ -411,7 +413,9 @@
   // Simple Accessors
 
   // Returns a WeakPtr to the ExtensionService.
-  base::WeakPtr<ExtensionService> AsWeakPtr() { return base::AsWeakPtr(this); }
+  base::WeakPtr<ExtensionService> AsExtensionServiceWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
 
   // Returns profile_ as a BrowserContext.
   content::BrowserContext* GetBrowserContext() const;
@@ -760,6 +764,8 @@
   AshExtensionKeeplistManager ash_keeplist_manager_;
 #endif
 
+  base::WeakPtrFactory<ExtensionService> weak_ptr_factory_{this};
+
   FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
                            DestroyingProfileClearsExtensions);
   FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, SetUnsetBlocklistInPrefs);
diff --git a/chrome/browser/extensions/fake_crx_installer.cc b/chrome/browser/extensions/fake_crx_installer.cc
index e3afeda..1239161 100644
--- a/chrome/browser/extensions/fake_crx_installer.cc
+++ b/chrome/browser/extensions/fake_crx_installer.cc
@@ -9,7 +9,7 @@
 namespace extensions {
 
 FakeCrxInstaller::FakeCrxInstaller(ExtensionService* frontend)
-    : CrxInstaller(frontend->AsWeakPtr(), nullptr, nullptr) {}
+    : CrxInstaller(frontend->AsExtensionServiceWeakPtr(), nullptr, nullptr) {}
 
 FakeCrxInstaller::~FakeCrxInstaller() = default;
 
diff --git a/chrome/browser/extensions/mock_crx_installer.cc b/chrome/browser/extensions/mock_crx_installer.cc
index 1c69577..81c2306f 100644
--- a/chrome/browser/extensions/mock_crx_installer.cc
+++ b/chrome/browser/extensions/mock_crx_installer.cc
@@ -7,7 +7,7 @@
 namespace extensions {
 
 MockCrxInstaller::MockCrxInstaller(ExtensionService* frontend)
-    : CrxInstaller(frontend->AsWeakPtr(), nullptr, nullptr) {}
+    : CrxInstaller(frontend->AsExtensionServiceWeakPtr(), nullptr, nullptr) {}
 
 MockCrxInstaller::~MockCrxInstaller() = default;
 
diff --git a/chrome/browser/extensions/test_extension_service.cc b/chrome/browser/extensions/test_extension_service.cc
index c81c699..0207ade49 100644
--- a/chrome/browser/extensions/test_extension_service.cc
+++ b/chrome/browser/extensions/test_extension_service.cc
@@ -9,7 +9,9 @@
 
 using extensions::Extension;
 
-TestExtensionService::~TestExtensionService() {}
+TestExtensionService::TestExtensionService() = default;
+
+TestExtensionService::~TestExtensionService() = default;
 
 extensions::PendingExtensionManager*
 TestExtensionService::pending_extension_manager() {
@@ -85,3 +87,8 @@
 void TestExtensionService::ReinstallProviderExtensions() {
   ADD_FAILURE();
 }
+
+base::WeakPtr<extensions::ExtensionServiceInterface>
+TestExtensionService::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
diff --git a/chrome/browser/extensions/test_extension_service.h b/chrome/browser/extensions/test_extension_service.h
index 7b010f95b..6425aed 100644
--- a/chrome/browser/extensions/test_extension_service.h
+++ b/chrome/browser/extensions/test_extension_service.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/extensions/extension_service.h"
 
 namespace extensions {
@@ -20,6 +21,7 @@
 // this and override the methods you care about.
 class TestExtensionService : public extensions::ExtensionServiceInterface {
  public:
+  TestExtensionService();
   ~TestExtensionService() override;
 
   // ExtensionServiceInterface implementation.
@@ -50,6 +52,11 @@
       const std::string& extension_id) override;
 
   void ReinstallProviderExtensions() override;
+
+  base::WeakPtr<ExtensionServiceInterface> AsWeakPtr() override;
+
+ private:
+  base::WeakPtrFactory<TestExtensionService> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_EXTENSIONS_TEST_EXTENSION_SERVICE_H_
diff --git a/chrome/browser/extensions/unpacked_installer.cc b/chrome/browser/extensions/unpacked_installer.cc
index 498c6f7..7444723d 100644
--- a/chrome/browser/extensions/unpacked_installer.cc
+++ b/chrome/browser/extensions/unpacked_installer.cc
@@ -83,7 +83,7 @@
 }
 
 UnpackedInstaller::UnpackedInstaller(ExtensionService* extension_service)
-    : service_weak_(extension_service->AsWeakPtr()),
+    : service_weak_(extension_service->AsExtensionServiceWeakPtr()),
       profile_(extension_service->profile()),
       require_modern_manifest_version_(true),
       be_noisy_on_failure_(true) {
diff --git a/chrome/browser/feedback/show_feedback_page.cc b/chrome/browser/feedback/show_feedback_page.cc
index 3a78cc2f7..99d1861 100644
--- a/chrome/browser/feedback/show_feedback_page.cc
+++ b/chrome/browser/feedback/show_feedback_page.cc
@@ -139,6 +139,7 @@
     case kFeedbackSourceMdSettingsAboutPage:
     case kFeedbackSourceOldSettingsAboutPage:
     case kFeedbackSourceQuickAnswers:
+    case kFeedbackSourceQuickOffice:
     case kFeedbackSourceSettingsPerformancePage:
       return true;
     default:
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 91985af..4ba956d 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2348,7 +2348,7 @@
       "jeffcchen",
       "chromeos-gfx-video@google.com"
     ],
-    "expiry_milestone": 110
+    "expiry_milestone": 130
   },
   {
     "name": "enable-gpu-rasterization",
@@ -2824,6 +2824,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "enable-power-sounds",
+    "owners": [ "afakhry", "bicioglu", "hongyulong", "nupurjain" ],
+    "expiry_milestone": 123
+  },
+  {
     "name": "enable-preinstalled-web-app-duplication-fixer",
     "owners": [ "alancutter" ],
     "expiry_milestone": 110
@@ -6396,12 +6401,12 @@
   {
     "name": "sync-standalone-invalidations",
     "owners": [ "treib", "rushans" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "sync-standalone-invalidations-wallet-and-offer",
     "owners": [ "treib", "rushans" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "sync-trusted-vault-passphrase-promo",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 5fe5ac0..7c926c42 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2837,6 +2837,10 @@
     "keyboard shortcuts and have the events routed directly to the website "
     "when in fullscreen mode.";
 
+const char kSystemSoundsName[] = "Power Sounds";
+const char kSystemSoundsDescription[] =
+    "Enable device charging and low battery warning sounds.";
+
 const char kStylusBatteryStatusName[] =
     "Show stylus battery stylus in the stylus tools menu";
 const char kStylusBatteryStatusDescription[] =
@@ -3636,9 +3640,6 @@
 const char kFeedStampName[] = "StAMP cards in the feed";
 const char kFeedStampDescription[] = "Enables StAMP cards in the feed.";
 
-const char kFeedIsAblatedName[] = "Feed ablation";
-const char kFeedIsAblatedDescription[] = "Enables feed ablation.";
-
 const char kFeedCloseRefreshName[] = "Feed-close refresh";
 const char kFeedCloseRefreshDescription[] =
     "Enables scheduling a background refresh of the feed following feed use.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f51043b..d8ff874 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1575,6 +1575,9 @@
 extern const char kSystemKeyboardLockName[];
 extern const char kSystemKeyboardLockDescription[];
 
+extern const char kSystemSoundsName[];
+extern const char kSystemSoundsDescription[];
+
 extern const char kSuggestionsWithSubStringMatchName[];
 extern const char kSuggestionsWithSubStringMatchDescription[];
 
@@ -2075,9 +2078,6 @@
 extern const char kFeedStampName[];
 extern const char kFeedStampDescription[];
 
-extern const char kFeedIsAblatedName[];
-extern const char kFeedIsAblatedDescription[];
-
 extern const char kFeedCloseRefreshName[];
 extern const char kFeedCloseRefreshDescription[];
 
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 e411cae..4323015 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
@@ -600,7 +600,6 @@
     public static final String WEB_APK_UNIQUE_ID = "WebApkUniqueId";
     public static final String XSURFACE_METRICS_REPORTING = "XsurfaceMetricsReporting";
     public static final String WEB_OTP_CROSS_DEVICE_SIMPLE_STRING = "WebOtpCrossDeviceSimpleString";
-    public static final String FEED_ABLATION = "FeedAblation";
     public static final String FEED_NO_VIEW_CACHE = "FeedNoViewCache";
     public static final String FEED_REPLACE_ALL = "FeedReplaceAll";
     public static final String FEED_SHOW_SIGN_IN_COMMAND = "FeedShowSignInCommand";
diff --git a/chrome/browser/google/google_brand_chromeos.cc b/chrome/browser/google/google_brand_chromeos.cc
index a870c3f4..234ace3 100644
--- a/chrome/browser/google/google_brand_chromeos.cc
+++ b/chrome/browser/google/google_brand_chromeos.cc
@@ -6,6 +6,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/task/thread_pool.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
@@ -27,7 +28,7 @@
 const base::FilePath::CharType kRLZBrandFilePath[] =
     FILE_PATH_LITERAL("/opt/oem/etc/BRAND_CODE");
 
-bool IsBrandValid(const std::string& brand) {
+bool IsBrandValid(base::StringPiece brand) {
   return !brand.empty();
 }
 
@@ -88,11 +89,10 @@
 void InitBrand(base::OnceClosure callback) {
   ::chromeos::system::StatisticsProvider* provider =
       ::chromeos::system::StatisticsProvider::GetInstance();
-  std::string brand;
-  const bool found = provider->GetMachineStatistic(
-      ::chromeos::system::kRlzBrandCodeKey, &brand);
-  if (found && IsBrandValid(brand)) {
-    SetBrand(std::move(callback), brand);
+  const absl::optional<base::StringPiece> brand =
+      provider->GetMachineStatistic(::chromeos::system::kRlzBrandCodeKey);
+  if (brand && IsBrandValid(brand.value())) {
+    SetBrand(std::move(callback), std::string(brand.value()));
     return;
   }
 
diff --git a/chrome/browser/history_clusters/entity_image_service.cc b/chrome/browser/history_clusters/entity_image_service.cc
index 06001aef..a5400545 100644
--- a/chrome/browser/history_clusters/entity_image_service.cc
+++ b/chrome/browser/history_clusters/entity_image_service.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/history_clusters/entity_image_service.h"
 
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/i18n/case_conversion.h"
 #include "base/memory/singleton.h"
@@ -56,6 +58,85 @@
   }
 };
 
+// A one-time use object that encapsulates tagging a vector of clusters with
+// entity images. Used to manage all the fetch jobs dispatched, and runs the
+// main callback after it's done.
+// TODO(tommycli): This is kind of janky and surely not what we want to do.
+// Replace this with a dedicated server-side service.
+class FetchJobManager {
+ public:
+  using ResultCallback =
+      base::OnceCallback<void(std::vector<history::Cluster>)>;
+
+  struct Request {
+    std::u16string query;
+    std::string entity_id;
+    history::ClusterVisit* visit;
+  };
+
+  explicit FetchJobManager(std::vector<history::Cluster>&& clusters)
+      : clusters_(clusters) {}
+
+  void Start(EntityImageService* service, ResultCallback callback) {
+    std::vector<Request> requests;
+    for (auto& cluster : clusters_) {
+      for (auto& visit : cluster.visits) {
+        // Only tag search visits for now.
+        const auto& search_terms =
+            visit.annotated_visit.content_annotations.search_terms;
+        if (!search_terms.empty()) {
+          // TODO(tommycli): Add entity_id once implemented.
+          requests.push_back({search_terms, "", &visit});
+        }
+      }
+    }
+
+    // If no requests needed, just early exit and give back the clusters.
+    if (requests.empty()) {
+      return FinishJob(std::move(callback));
+    }
+
+    // This encapsulates the final callback and is called after all the requests
+    // are completed.
+    auto finish_callback = base::BarrierClosure(
+        requests.size(),
+        base::BindOnce(&FetchJobManager::FinishJob, weak_factory_.GetWeakPtr(),
+                       std::move(callback)));
+
+    for (auto& request : requests) {
+      service->FetchImageFor(
+          request.query, request.entity_id,
+          base::BindOnce(&FetchJobManager::OnImageFetchedForVisit,
+                         weak_factory_.GetWeakPtr(), request.visit)
+              .Then(finish_callback));
+    }
+  }
+
+ private:
+  // Populates the cluster visit's field. This is a member method and not a
+  // free function, because `visit` points to memory owned by this object.
+  void OnImageFetchedForVisit(history::ClusterVisit* visit,
+                              const GURL& image_url) {
+    visit->image_url = image_url;
+  }
+
+  void FinishJob(ResultCallback callback) {
+    std::move(callback).Run(std::move(clusters_));
+  }
+
+  std::vector<history::Cluster> clusters_;
+  base::WeakPtrFactory<FetchJobManager> weak_factory_{this};
+};
+
+// An anonymous function whose only job is to scope the lifetime of
+// ClusterVectorImageTaggingJob, then call `callback` with `clusters`.
+void DeleteManagerAndRunCallback(
+    std::unique_ptr<FetchJobManager> job,
+    base::OnceCallback<void(std::vector<history::Cluster>)> callback,
+    std::vector<history::Cluster> clusters) {
+  std::move(callback).Run(clusters);
+}
+
 }  // namespace
 
 // A one-time use object that uses Suggest to get an image URL corresponding
@@ -178,14 +259,28 @@
   return EntityImageServiceFactory::GetForProfile(profile);
 }
 
+void EntityImageService::PopulateEntityImagesFor(
+    std::vector<history::Cluster> clusters,
+    base::OnceCallback<void(std::vector<history::Cluster>)> callback) {
+  if (!GetConfig().images || !url_consent_helper_ ||
+      !url_consent_helper_->IsEnabled()) {
+    return std::move(callback).Run(std::move(clusters));
+  }
+
+  auto manager = std::make_unique<FetchJobManager>(std::move(clusters));
+  // Use a raw pointer temporary so we can give ownership of the unique_ptr to
+  // the callback and have a well defined object lifetime.
+  auto* manager_ptr = manager.get();
+  manager_ptr->Start(
+      this, base::BindOnce(&DeleteManagerAndRunCallback, std::move(manager),
+                           std::move(callback)));
+}
+
 bool EntityImageService::FetchImageFor(const std::u16string& search_query,
                                        const std::string& entity_id,
                                        ResultCallback callback) {
-  if (!GetConfig().images)
-    return false;
-
-  if (!url_consent_helper_ || !url_consent_helper_->IsEnabled())
-    return false;
+  DCHECK(GetConfig().images);
+  DCHECK(url_consent_helper_ && url_consent_helper_->IsEnabled());
 
   auto fetcher = std::make_unique<SuggestEntityImageURLFetcher>(
       profile_, &autocomplete_provider_client_, search_query, entity_id);
diff --git a/chrome/browser/history_clusters/entity_image_service.h b/chrome/browser/history_clusters/entity_image_service.h
index dafd4a9..6e7268e0 100644
--- a/chrome/browser/history_clusters/entity_image_service.h
+++ b/chrome/browser/history_clusters/entity_image_service.h
@@ -7,9 +7,11 @@
 
 #include <string>
 
+#include "base/functional/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/history/core/browser/history_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
 
@@ -32,6 +34,14 @@
   // Gets the fetcher associated with `profile`. Always succeeds.
   static EntityImageService* Get(Profile* profile);
 
+  // Populates entity images into the `image_url` of any eligible visits within
+  // every cluster in `clusters`. `clusters` should be moved into the parameter.
+  // `callback` is called when we're done, and it can be called synchronously
+  // if there's nothing to do.
+  void PopulateEntityImagesFor(
+      std::vector<history::Cluster> clusters,
+      base::OnceCallback<void(std::vector<history::Cluster>)> callback);
+
   // Fetches an image appropriate for `search_query` and `entity_id`, returning
   // the result asynchronously to `callback`. Returns false if we can't do it
   // for configuration or privacy reasons.
diff --git a/chrome/browser/history_clusters/history_clusters_service_factory.cc b/chrome/browser/history_clusters/history_clusters_service_factory.cc
index 0a9ffbc..4d35b899 100644
--- a/chrome/browser/history_clusters/history_clusters_service_factory.cc
+++ b/chrome/browser/history_clusters/history_clusters_service_factory.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/optimization_guide/page_content_annotations_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "components/history_clusters/core/history_clusters_service.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
@@ -41,6 +42,7 @@
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(PageContentAnnotationsServiceFactory::GetInstance());
   DependsOn(site_engagement::SiteEngagementServiceFactory::GetInstance());
+  DependsOn(TemplateURLServiceFactory::GetInstance());
   DependsOn(OptimizationGuideKeyedServiceFactory::GetInstance());
 }
 
@@ -63,5 +65,6 @@
       g_browser_process->GetApplicationLocale(), history_service,
       PageContentAnnotationsServiceFactory::GetForProfile(profile),
       url_loader_factory, site_engagement::SiteEngagementService::Get(profile),
+      TemplateURLServiceFactory::GetForProfile(profile),
       OptimizationGuideKeyedServiceFactory::GetForProfile(profile));
 }
diff --git a/chrome/browser/media/media_engagement_preloaded_list.cc b/chrome/browser/media/media_engagement_preloaded_list.cc
index 9c4b2a4..2cbb677 100644
--- a/chrome/browser/media/media_engagement_preloaded_list.cc
+++ b/chrome/browser/media/media_engagement_preloaded_list.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/media/media_engagement_preloaded_list.h"
 
 #include "base/files/file_util.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
@@ -15,12 +14,6 @@
 #include "url/url_canon.h"
 #include "url/url_constants.h"
 
-const char MediaEngagementPreloadedList::kHistogramCheckResultName[] =
-    "Media.Engagement.PreloadedList.CheckResult";
-
-const char MediaEngagementPreloadedList::kHistogramLoadResultName[] =
-    "Media.Engagement.PreloadedList.LoadResult";
-
 // static
 MediaEngagementPreloadedList* MediaEngagementPreloadedList::GetInstance() {
   static base::NoDestructor<MediaEngagementPreloadedList> instance;
@@ -51,21 +44,18 @@
 
   // Check the file exists.
   if (!base::PathExists(path)) {
-    RecordLoadResult(LoadResult::kFileNotFound);
     return false;
   }
 
   // Read the file to a string.
   std::string file_data;
   if (!base::ReadFileToString(path, &file_data)) {
-    RecordLoadResult(LoadResult::kFileReadFailed);
     return false;
   }
 
   // Load the preloaded list into a proto message.
   chrome_browser_media::PreloadedData message;
   if (!message.ParseFromString(file_data)) {
-    RecordLoadResult(LoadResult::kParseProtoFailed);
     return false;
   }
 
@@ -74,7 +64,6 @@
       message.dafsa().c_str(),
       message.dafsa().c_str() + message.dafsa().length());
 
-  RecordLoadResult(LoadResult::kLoaded);
   is_loaded_ = true;
   return true;
 }
@@ -85,13 +74,11 @@
 
   // Check if we have loaded the data.
   if (!loaded()) {
-    RecordCheckResult(CheckResult::kListNotLoaded);
     return false;
   }
 
   // Check if the data is empty.
   if (empty()) {
-    RecordCheckResult(CheckResult::kListEmpty);
     return false;
   }
 
@@ -112,27 +99,21 @@
     case DafsaResult::kFoundHttpsOnly:
       // Only HTTPS is allowed by default.
       if (origin.scheme() == url::kHttpsScheme) {
-        RecordCheckResult(CheckResult::kFoundHttpsOnly);
         return true;
-      } else {
-        RecordCheckResult(CheckResult::kFoundHttpsOnlyButWasHttp);
       }
       break;
     case DafsaResult::kFoundHttpOrHttps:
       // Allow either HTTP or HTTPS.
       if (origin.scheme() == url::kHttpScheme ||
           origin.scheme() == url::kHttpsScheme) {
-        RecordCheckResult(CheckResult::kFoundHttpOrHttps);
         return true;
       }
       break;
     case DafsaResult::kNotFound:
-      RecordCheckResult(CheckResult::kNotFound);
       break;
   }
 
-  // If we do not match then we should record a not found result and return
-  // false.
+  // If we do not match then we should return false.
   return false;
 }
 
@@ -143,13 +124,3 @@
       net::LookupStringInFixedSet(dafsa_.data(), dafsa_.size(), input.c_str(),
                                   input.size()));
 }
-
-void MediaEngagementPreloadedList::RecordLoadResult(LoadResult result) {
-  UMA_HISTOGRAM_ENUMERATION(kHistogramLoadResultName, result,
-                            LoadResult::kCount);
-}
-
-void MediaEngagementPreloadedList::RecordCheckResult(CheckResult result) const {
-  UMA_HISTOGRAM_ENUMERATION(kHistogramCheckResultName, result,
-                            CheckResult::kCount);
-}
diff --git a/chrome/browser/media/media_engagement_preloaded_list.h b/chrome/browser/media/media_engagement_preloaded_list.h
index 7b89548..df155f3 100644
--- a/chrome/browser/media/media_engagement_preloaded_list.h
+++ b/chrome/browser/media/media_engagement_preloaded_list.h
@@ -46,55 +46,6 @@
  protected:
   friend class MediaEngagementPreloadedListTest;
 
-  // The names of the CheckResult and LoadResult histograms.
-  static const char kHistogramCheckResultName[];
-  static const char kHistogramLoadResultName[];
-
-  // The result of the CheckStringIsPresent operation. This enum is used to
-  // record a histogram and should not be renumbered.
-  enum class CheckResult {
-    // The check succeeded and the string was found in the data.
-    kFoundHttpsOnly = 0,
-
-    // The check succeeded but the string was not found in the data.
-    kNotFound,
-
-    // The check failed because the list is empty.
-    kListEmpty,
-
-    // The check failed because the list has not been loaded.
-    kListNotLoaded,
-
-    // The check succeeded, the string was found and it had metadata that it
-    // allows insecure origins.
-    kFoundHttpOrHttps,
-
-    // The check succeeded, the string was found but it was https only and the
-    // origin was insecure.
-    kFoundHttpsOnlyButWasHttp,
-
-    kCount
-  };
-
-  // The result of the LoadFromFile operation. This enum is used to record
-  // a histogram and should not be renumbered.
-  enum class LoadResult {
-    // The list was loaded successfully.
-    kLoaded = 0,
-
-    // The list was not loaded because the file was not found.
-    kFileNotFound,
-
-    // The list was not loaded because the file could not be read.
-    kFileReadFailed,
-
-    // The list was not loaded because the proto stored in the file could not be
-    // parsed.
-    kParseProtoFailed,
-
-    kCount
-  };
-
   enum class DafsaResult {
     // The string was not found.
     kNotFound = -1,
@@ -109,12 +60,6 @@
   // Checks if |input| is present in the preloaded data.
   DafsaResult CheckStringIsPresent(const std::string& input) const;
 
-  // Records |result| to the LoadResult histogram.
-  void RecordLoadResult(LoadResult result);
-
-  // Records |result| to the CheckResult histogram.
-  void RecordCheckResult(CheckResult result) const;
-
  private:
   // The preloaded data in dafsa format.
   std::vector<unsigned char> dafsa_;
diff --git a/chrome/browser/media/media_engagement_preloaded_list_unittest.cc b/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
index 7218437..2ae204e 100644
--- a/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
+++ b/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/files/file_path.h"
 #include "base/path_service.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/base/testing_profile.h"
@@ -75,84 +74,7 @@
 
   bool IsEmpty() { return preloaded_list_->empty(); }
 
-  void ExpectCheckResultFoundHttpsOnlyCount(int count) {
-    ExpectCheckResultCount(
-        MediaEngagementPreloadedList::CheckResult::kFoundHttpsOnly, count);
-  }
-
-  void ExpectCheckResultFoundHttpsButWasHttpOnlyCount(int count) {
-    ExpectCheckResultCount(
-        MediaEngagementPreloadedList::CheckResult::kFoundHttpsOnlyButWasHttp,
-        count);
-  }
-
-  void ExpectCheckResultFoundHttpOrHttpsCount(int count) {
-    ExpectCheckResultCount(
-        MediaEngagementPreloadedList::CheckResult::kFoundHttpOrHttps, count);
-  }
-
-  void ExpectCheckResultNotFoundCount(int count) {
-    ExpectCheckResultCount(MediaEngagementPreloadedList::CheckResult::kNotFound,
-                           count);
-  }
-
-  void ExpectCheckResultNotLoadedCount(int count) {
-    ExpectCheckResultCount(
-        MediaEngagementPreloadedList::CheckResult::kListNotLoaded, count);
-  }
-
-  void ExpectCheckResultListEmptyCount(int count) {
-    ExpectCheckResultCount(
-        MediaEngagementPreloadedList::CheckResult::kListEmpty, count);
-  }
-
-  void ExpectCheckResultTotal(int total) {
-    histogram_tester_.ExpectTotalCount(
-        MediaEngagementPreloadedList::kHistogramCheckResultName, total);
-  }
-
-  void ExpectLoadResultLoaded() {
-    ExpectLoadResult(MediaEngagementPreloadedList::LoadResult::kLoaded);
-  }
-
-  void ExpectLoadResultFileNotFound() {
-    ExpectLoadResult(MediaEngagementPreloadedList::LoadResult::kFileNotFound);
-  }
-
-  void ExpectLoadResultFileReadFailed() {
-    ExpectLoadResult(MediaEngagementPreloadedList::LoadResult::kFileReadFailed);
-  }
-
-  void ExpectLoadResultParseProtoFailed() {
-    ExpectLoadResult(
-        MediaEngagementPreloadedList::LoadResult::kParseProtoFailed);
-  }
-
-  const base::HistogramTester& histogram_tester() const {
-    return histogram_tester_;
-  }
-
- protected:
-  void ExpectLoadResult(MediaEngagementPreloadedList::LoadResult result) {
-    histogram_tester_.ExpectBucketCount(
-        MediaEngagementPreloadedList::kHistogramLoadResultName,
-        static_cast<int>(result), 1);
-
-    // Ensure not other results were logged.
-    histogram_tester_.ExpectTotalCount(
-        MediaEngagementPreloadedList::kHistogramLoadResultName, 1);
-  }
-
-  void ExpectCheckResultCount(MediaEngagementPreloadedList::CheckResult result,
-                              int count) {
-    histogram_tester_.ExpectBucketCount(
-        MediaEngagementPreloadedList::kHistogramCheckResultName,
-        static_cast<int>(result), count);
-  }
-
   std::unique_ptr<MediaEngagementPreloadedList> preloaded_list_;
-
-  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(MediaEngagementPreloadedListTest, CheckOriginIsPresent) {
@@ -161,31 +83,17 @@
   EXPECT_TRUE(IsLoaded());
   EXPECT_FALSE(IsEmpty());
 
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultLoaded();
-
   // Check some origins that are not in the list.
-  ExpectCheckResultTotal(0);
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://example.com")));
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://example.org:1234")));
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://test--3ya.com")));
   EXPECT_TRUE(CheckOriginIsPresent(GURL("http://123.123.123.123")));
 
-  // Check they were recorded on the histogram.
-  ExpectCheckResultTotal(4);
-  ExpectCheckResultFoundHttpsOnlyCount(3);
-  ExpectCheckResultFoundHttpOrHttpsCount(1);
-
   // Check some origins that are not in the list.
   EXPECT_FALSE(CheckOriginIsPresent(GURL("https://example.org")));
   EXPECT_FALSE(CheckOriginIsPresent(GURL("http://example.com")));
   EXPECT_FALSE(CheckOriginIsPresent(GURL("http://123.123.123.124")));
 
-  // Check they were recorded on the histogram.
-  ExpectCheckResultTotal(7);
-  ExpectCheckResultNotFoundCount(2);
-  ExpectCheckResultFoundHttpsButWasHttpOnlyCount(1);
-
   // Make sure only the full origin matches.
   EXPECT_FALSE(CheckStringIsPresent("123"));
   EXPECT_FALSE(CheckStringIsPresent("http"));
@@ -197,15 +105,6 @@
       LoadFromFile(GetAbsolutePathToGeneratedTestFile(kMissingFilePath)));
   EXPECT_FALSE(IsLoaded());
   EXPECT_TRUE(IsEmpty());
-
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultFileNotFound();
-
-  // Test checking an origin and make sure the result is recorded to the
-  // histogram.
-  EXPECT_FALSE(CheckOriginIsPresent(GURL("https://example.com")));
-  ExpectCheckResultTotal(1);
-  ExpectCheckResultNotLoadedCount(1);
 }
 
 #if BUILDFLAG(IS_FUCHSIA)
@@ -219,14 +118,9 @@
   EXPECT_FALSE(IsLoaded());
   EXPECT_TRUE(IsEmpty());
 
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultFileReadFailed();
-
   // Test checking an origin and make sure the result is recorded to the
   // histogram.
   EXPECT_FALSE(CheckOriginIsPresent(GURL("https://example.com")));
-  ExpectCheckResultTotal(1);
-  ExpectCheckResultNotLoadedCount(1);
 }
 
 TEST_F(MediaEngagementPreloadedListTest, LoadBadFormatFile) {
@@ -235,29 +129,15 @@
   EXPECT_FALSE(IsLoaded());
   EXPECT_TRUE(IsEmpty());
 
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultParseProtoFailed();
-
   // Test checking an origin and make sure the result is recorded to the
   // histogram.
   EXPECT_FALSE(CheckOriginIsPresent(GURL("https://example.com")));
-  ExpectCheckResultTotal(1);
-  ExpectCheckResultNotLoadedCount(1);
 }
 
 TEST_F(MediaEngagementPreloadedListTest, LoadEmptyFile) {
   ASSERT_TRUE(LoadFromFile(GetAbsolutePathToGeneratedTestFile(kEmptyFilePath)));
   EXPECT_TRUE(IsLoaded());
   EXPECT_TRUE(IsEmpty());
-
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultLoaded();
-
-  // Test checking an origin and make sure the result is recorded to the
-  // histogram.
-  EXPECT_FALSE(CheckOriginIsPresent(GURL("https://example.com")));
-  ExpectCheckResultTotal(1);
-  ExpectCheckResultListEmptyCount(1);
 }
 
 TEST_F(MediaEngagementPreloadedListTest, CheckOriginIsPresent_UnsecureSchemes) {
@@ -266,25 +146,15 @@
   EXPECT_TRUE(IsLoaded());
   EXPECT_FALSE(IsEmpty());
 
-  // Check the load result was recorded on the histogram.
-  ExpectLoadResultLoaded();
-
   // An origin that has both HTTP and HTTPS entries should allow either.
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://google.com")));
   EXPECT_TRUE(CheckOriginIsPresent(GURL("http://google.com")));
-  ExpectCheckResultTotal(2);
-  ExpectCheckResultFoundHttpOrHttpsCount(2);
 
   // An origin that only has a HTTP origin should allow either.
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://123.123.123.123")));
   EXPECT_TRUE(CheckOriginIsPresent(GURL("http://123.123.123.123")));
-  ExpectCheckResultTotal(4);
-  ExpectCheckResultFoundHttpOrHttpsCount(4);
 
   // An origin that has only HTTPS should only allow HTTPS.
   EXPECT_TRUE(CheckOriginIsPresent(GURL("https://example.com")));
   EXPECT_FALSE(CheckOriginIsPresent(GURL("http://example.com")));
-  ExpectCheckResultTotal(6);
-  ExpectCheckResultFoundHttpsOnlyCount(1);
-  ExpectCheckResultFoundHttpsButWasHttpOnlyCount(1);
 }
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index 98de4754..48faac7 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -964,7 +964,9 @@
 
   void SetUpInProcessBrowserTestFixture() override {
     if (enable_hidpi()) {
-      feature_list_.InitWithFeatures({media::kWebContentsCaptureHiDpi}, {});
+      feature_list_.InitAndEnableFeature(media::kWebContentsCaptureHiDpi);
+    } else {
+      feature_list_.InitAndDisableFeature(media::kWebContentsCaptureHiDpi);
     }
 
     WebRtcTestBase::SetUpInProcessBrowserTestFixture();
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index db5c701..ef4cdf3f 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -193,6 +193,10 @@
   std::unique_ptr<optimization_guide::OptimizationGuideStore>
       prediction_model_and_features_store_;
 
+  // The logger that plumbs the debug logs to the optimization guide
+  // internals page. Must outlive `prediction_manager_`.
+  std::unique_ptr<OptimizationGuideLogger> optimization_guide_logger_;
+
   // Manages the storing, loading, and evaluating of optimization target
   // prediction models.
   std::unique_ptr<optimization_guide::PredictionManager> prediction_manager_;
@@ -205,10 +209,6 @@
   // The tab URL provider to use for fetching information for the user's active
   // tabs. Will be null if the user is off the record.
   std::unique_ptr<optimization_guide::TabUrlProvider> tab_url_provider_;
-
-  // The logger that plumbs the debug logs to the optimization guide
-  // internals page.
-  std::unique_ptr<OptimizationGuideLogger> optimization_guide_logger_;
 };
 
 #endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_KEYED_SERVICE_H_
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index fae947b..a4aa237 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -1754,7 +1754,16 @@
       driver->AsWeakPtr(), observer_, web_contents(),
       driver->render_frame_host());
   popup_controller_->UpdateTypedPassword(ui_data.user_typed_password);
-  popup_controller_->Show(PasswordGenerationPopupController::kOfferGeneration);
+
+  // TODO(crbug.com/1345766): Add separate flag for calculating strength and use
+  // this one only when UI needs to be displayed.
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordStrengthIndicator)) {
+    popup_controller_->UpdatePopupBasedOnTypedPasswordStrength();
+  } else {
+    popup_controller_->Show(
+        PasswordGenerationPopupController::kOfferGeneration);
+  }
 }
 
 gfx::RectF ChromePasswordManagerClient::TransformToRootCoordinates(
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 4d35e3380..a92551b9 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1066,6 +1066,7 @@
   memory::EnterpriseMemoryLimitPrefObserver::RegisterPrefs(registry);
   metrics::RegisterDemographicsLocalStatePrefs(registry);
   network_time::NetworkTimeTracker::RegisterPrefs(registry);
+  optimization_guide::prefs::RegisterLocalStatePrefs(registry);
   password_manager::PasswordManager::RegisterLocalPrefs(registry);
   policy::BrowserPolicyConnector::RegisterPrefs(registry);
   policy::ManagementService::RegisterLocalStatePrefs(registry);
diff --git a/chrome/browser/printing/print_backend_service_manager.cc b/chrome/browser/printing/print_backend_service_manager.cc
index aa556a0..2fa2bf9 100644
--- a/chrome/browser/printing/print_backend_service_manager.cc
+++ b/chrome/browser/printing/print_backend_service_manager.cc
@@ -411,6 +411,26 @@
                      base::Unretained(this), context));
 }
 
+void PrintBackendServiceManager::Cancel(
+    const std::string& printer_name,
+    int document_cookie,
+    mojom::PrintBackendService::CancelCallback callback) {
+  CallbackContext context;
+  auto& service = GetServiceAndCallbackContext(
+      printer_name, ClientType::kPrintDocument, context);
+
+  SaveCallback(GetRemoteSavedCancelCallbacks(context.is_sandboxed),
+               context.remote_id, context.saved_callback_id,
+               std::move(callback));
+
+  SetCrashKeys(printer_name);
+
+  LogCallToRemote("Cancel", context);
+  service->Cancel(document_cookie,
+                  base::BindOnce(&PrintBackendServiceManager::OnDidCancel,
+                                 base::Unretained(this), context));
+}
+
 bool PrintBackendServiceManager::PrinterDriverFoundToRequireElevatedPrivilege(
     const std::string& printer_name) const {
   return drivers_requiring_elevated_privilege_.contains(printer_name);
@@ -922,6 +942,7 @@
                     remote_id, mojom::ResultCode::kFailed);
   RunSavedCallbacks(GetRemoteSavedDocumentDoneCallbacks(sandboxed), remote_id,
                     mojom::ResultCode::kFailed);
+  RunSavedCallbacks(GetRemoteSavedCancelCallbacks(sandboxed), remote_id);
 }
 
 PrintBackendServiceManager::RemoteSavedEnumeratePrintersCallbacks&
@@ -1008,6 +1029,12 @@
                    : unsandboxed_saved_document_done_callbacks_;
 }
 
+PrintBackendServiceManager::RemoteSavedCancelCallbacks&
+PrintBackendServiceManager::GetRemoteSavedCancelCallbacks(bool sandboxed) {
+  return sandboxed ? sandboxed_saved_cancel_callbacks_
+                   : unsandboxed_saved_cancel_callbacks_;
+}
+
 const mojo::Remote<mojom::PrintBackendService>&
 PrintBackendServiceManager::GetServiceAndCallbackContext(
     const std::string& printer_name,
@@ -1151,6 +1178,12 @@
                       context.remote_id, context.saved_callback_id, result);
 }
 
+void PrintBackendServiceManager::OnDidCancel(const CallbackContext& context) {
+  LogCallbackFromRemote("Cancel", context);
+  ServiceCallbackDone(GetRemoteSavedCancelCallbacks(context.is_sandboxed),
+                      context.remote_id, context.saved_callback_id);
+}
+
 template <class T>
 void PrintBackendServiceManager::RunSavedCallbacksStructResult(
     RemoteSavedStructCallbacks<T>& saved_callbacks,
diff --git a/chrome/browser/printing/print_backend_service_manager.h b/chrome/browser/printing/print_backend_service_manager.h
index 08f05c90..9371c475 100644
--- a/chrome/browser/printing/print_backend_service_manager.h
+++ b/chrome/browser/printing/print_backend_service_manager.h
@@ -132,6 +132,9 @@
   void DocumentDone(const std::string& printer_name,
                     int document_cookie,
                     mojom::PrintBackendService::DocumentDoneCallback callback);
+  void Cancel(const std::string& printer_name,
+              int document_cookie,
+              mojom::PrintBackendService::CancelCallback callback);
 
   // Query if printer driver has been found to require elevated privilege in
   // order to have print queries/commands succeed.
@@ -230,6 +233,7 @@
       RemoteSavedCallbacks<mojom::ResultCode>;
   using RemoteSavedDocumentDoneCallbacks =
       RemoteSavedCallbacks<mojom::ResultCode>;
+  using RemoteSavedCancelCallbacks = RemoteSavedCallbacks<>;
 
   // Bundle of the `PrintBackendService` and its sandboxed/unsandboxed host
   // remotes.
@@ -363,6 +367,7 @@
   GetRemoteSavedRenderPrintedDocumentCallbacks(bool sandboxed);
   RemoteSavedDocumentDoneCallbacks& GetRemoteSavedDocumentDoneCallbacks(
       bool sandboxed);
+  RemoteSavedCancelCallbacks& GetRemoteSavedCancelCallbacks(bool sandboxed);
 
   // Helper function to get the service and initialize a `context` for a given
   // `printer_name`.
@@ -415,6 +420,7 @@
                                   mojom::ResultCode result);
   void OnDidDocumentDone(const CallbackContext& context,
                          mojom::ResultCode result);
+  void OnDidCancel(const CallbackContext& context);
 
   // Helper functions to run outstanding callbacks when a remote has become
   // disconnected.
@@ -509,6 +515,8 @@
       unsandboxed_saved_render_printed_document_callbacks_;
   RemoteSavedDocumentDoneCallbacks sandboxed_saved_document_done_callbacks_;
   RemoteSavedDocumentDoneCallbacks unsandboxed_saved_document_done_callbacks_;
+  RemoteSavedCancelCallbacks sandboxed_saved_cancel_callbacks_;
+  RemoteSavedCancelCallbacks unsandboxed_saved_cancel_callbacks_;
 
   // Set of printer drivers which require elevated permissions to operate.
   // It is expected that most print drivers will succeed with the preconfigured
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index 02f3002..465941d 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -128,7 +128,8 @@
     base::RepeatingCallback<void(mojom::ResultCode result)>;
 using OnDidDocumentDoneCallback =
     base::RepeatingCallback<void(mojom::ResultCode result)>;
-using OnDidShowErrorDialog = base::RepeatingCallback<void()>;
+using OnDidCancelCallback = base::RepeatingClosure;
+using OnDidShowErrorDialog = base::RepeatingClosure;
 
 #endif  // BUILDFLAG(ENABLE_OOP_PRINTING)
 
@@ -2298,6 +2299,7 @@
 #endif
     OnDidRenderPrintedDocumentCallback did_render_printed_document_callback;
     OnDidDocumentDoneCallback did_document_done_callback;
+    OnDidCancelCallback did_cancel_callback;
   };
 
   TestPrintJobWorkerOop(content::GlobalRenderFrameHostId rfh_id,
@@ -2369,6 +2371,15 @@
     callbacks_->did_document_done_callback.Run(result);
   }
 
+  void OnDidCancel(scoped_refptr<PrintJob> job) override {
+    DVLOG(1) << "Observed: cancel";
+    // Must not use `std::move(job)`, as that could potentially cause the `job`
+    // (and consequentially `this`) to be destroyed before
+    // `did_cancel_callback` is run.
+    PrintJobWorkerOop::OnDidCancel(job);
+    callbacks_->did_cancel_callback.Run();
+  }
+
   raw_ptr<PrintCallbacks> callbacks_;
 };
 #endif  // BUILDFLAG(ENABLE_OOP_PRINTING)
@@ -2430,6 +2441,10 @@
           base::BindRepeating(
               &SystemAccessProcessPrintBrowserTestBase::OnDidDocumentDone,
               base::Unretained(this));
+      test_print_job_worker_oop_callbacks_.did_cancel_callback =
+          base::BindRepeating(
+              &SystemAccessProcessPrintBrowserTestBase::OnDidCancel,
+              base::Unretained(this));
     } else {
       test_print_job_worker_callbacks_.did_use_default_settings_callback =
           base::BindRepeating(
@@ -2620,6 +2635,8 @@
     return document_done_result_;
   }
 
+  int cancel_count() const { return cancel_count_; }
+
   int print_job_construction_count() const {
     return print_job_construction_count_;
   }
@@ -2711,6 +2728,11 @@
     CheckForQuit();
   }
 
+  void OnDidCancel() {
+    ++cancel_count_;
+    CheckForQuit();
+  }
+
   void OnDidDestroyPrintJob() {
     ++print_job_destruction_count_;
     CheckForQuit();
@@ -2760,6 +2782,7 @@
   mojom::ResultCode render_printed_document_result_ =
       mojom::ResultCode::kFailed;
   mojom::ResultCode document_done_result_ = mojom::ResultCode::kFailed;
+  int cancel_count_ = 0;
   int print_job_construction_count_ = 0;
   int print_job_destruction_count_ = 0;
 };
@@ -2985,14 +3008,17 @@
   // sequence for this is:
   // 1.  A print job is started.
   // 2.  Spooling to send the render data will fail.  An error dialog is shown.
-  // 3.  Wait for the one print job to be destroyed, to ensure printing
+  // 3.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 4.  Wait for the one print job to be destroyed, to ensure printing
   //     finished cleanly before completing the test.
-  SetNumExpectedMessages(/*num=*/3);
+  SetNumExpectedMessages(/*num=*/4);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
@@ -3030,15 +3056,20 @@
     // The expected events for this are:
     // 1.  A print job is started, but that fails.
     // 2.  An error dialog is shown.
-    // 3.  Wait for the one print job to be destroyed, to ensure printing
+    // 3.  The print job is canceled.  The callback from the service could occur
+    //     after the print job has been destroyed.
+    // 4.  Wait for the one print job to be destroyed, to ensure printing
     //     finished cleanly before completing the test.
-    SetNumExpectedMessages(/*num=*/3);
+    SetNumExpectedMessages(/*num=*/4);
   }
 
   PrintAfterPreviewIsReadyAndLoaded();
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kFailed);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  // No tracking of cancel for in-browser tests, only for OOP.
+  if (GetParam() != PrintBackendFeatureVariation::kInBrowserProcess)
+    EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
@@ -3103,14 +3134,17 @@
   // 1.  A print job is started, but has an access-denied error.
   // 2.  A retry to start the print job with adjusted access will still fail.
   // 3.  An error dialog is shown.
-  // 4.  Wait for the one print job to be destroyed, to ensure printing
+  // 4.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 5.  Wait for the one print job to be destroyed, to ensure printing
   //     finished cleanly before completing the test.
-  SetNumExpectedMessages(/*num=*/4);
+  SetNumExpectedMessages(/*num=*/5);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kAccessDenied);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
@@ -3135,9 +3169,11 @@
   // 1.  A print job is started.
   // 2.  Rendering for 1 page of document of content fails with access denied.
   // 3.  An error dialog is shown.
-  // 4.  Wait for the one print job to be destroyed, to ensure printing
+  // 4.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 5.  Wait for the one print job to be destroyed, to ensure printing
   //     finished cleanly before completing the test.
-  SetNumExpectedMessages(/*num=*/4);
+  SetNumExpectedMessages(/*num=*/5);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
@@ -3145,13 +3181,12 @@
   EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kAccessDenied);
   EXPECT_EQ(render_printed_page_count(), 0);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
-// TODO(crbug.com/1326580):  Enable test once use-after-free after a failed
-// call is avoided.
 IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
-                       DISABLED_StartPrintingMultipageMidJobError) {
+                       StartPrintingMultipageMidJobError) {
   AddPrinter("printer1");
   SetPrinterNameForSubsequentContexts("printer1");
   // Delay rendering until all pages have been sent, to avoid any race
@@ -3170,17 +3205,31 @@
   ASSERT_TRUE(web_contents);
   SetUpPrintViewManager(web_contents);
 
-  // TODO(crbug.com/1326580):  Update behavior description after UAF during
-  // error processing in the PrintBackendService is resolved.  In meantime
-  // replicate the expected message count from StartPrintingMultipage test.
-  SetNumExpectedMessages(/*num=*/6);
+  // The expected events for this are:
+  // 1.  Start the print job.
+  // 2.  First page render callback shows success.
+  // 3.  Second page render callback shows failure.  Will start failure
+  //     processing to cancel the print job.
+  // 4.  A printing error dialog is displayed.
+  // 5.  Third page render callback will show it was canceled (due to prior
+  //     failure).  This is disregarded by the browser, since the job has
+  //     already been canceled.
+  // 6.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 7.  Wait for the one print job to be destroyed, to ensure printing
+  //     finished cleanly before completing the test.
+  SetNumExpectedMessages(/*num=*/7);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
-  EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kFailed);
-  // TODO(crbug.com/1326580):  Update remaining behavior checks after UAF
-  // during error processing in the PrintBackendService is resolved.
+  // First failure page is `kFailed`, but is followed by another page with
+  // status `kCanceled`.
+  EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kCanceled);
+  EXPECT_EQ(render_printed_page_count(), 1);
+  EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
+  EXPECT_EQ(print_job_destruction_count(), 1);
 }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -3206,15 +3255,18 @@
   // 1.  A print job is started.
   // 2.  Rendering for 1 page of document of content fails with access denied.
   // 3.  An error dialog is shown.
-  // 4.  Wait for the one print job to be destroyed, to ensure printing
+  // 4.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 5.  Wait for the one print job to be destroyed, to ensure printing
   //     finished cleanly before completing the test.
-  SetNumExpectedMessages(/*num=*/4);
+  SetNumExpectedMessages(/*num=*/5);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
   EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kAccessDenied);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 #endif  // !BUILDFLAG(IS_WIN)
@@ -3240,9 +3292,11 @@
   // 2.  Rendering for 1 page of document of content.
   // 3.  Document done results in an access-denied error.
   // 4.  An error dialog is shown.
-  // 5.  Wait for the one print job to be destroyed, to ensure printing
+  // 5.  The print job is canceled.  The callback from the service could occur
+  //     after the print job has been destroyed.
+  // 6.  Wait for the one print job to be destroyed, to ensure printing
   //     finished cleanly before completing the test.
-  SetNumExpectedMessages(/*num=*/5);
+  SetNumExpectedMessages(/*num=*/6);
 
   PrintAfterPreviewIsReadyAndLoaded();
 
@@ -3257,6 +3311,7 @@
 #endif
   EXPECT_EQ(document_done_result(), mojom::ResultCode::kAccessDenied);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
@@ -3409,12 +3464,14 @@
     // 2.  Asks user for settings.
     // 3.  A print job is started, which fails.
     // 4.  An error dialog is shown.
-    // 5.  Wait for the one print job to be destroyed, to ensure printing
+    // 5.  The print job is canceled.  The callback from the service could occur
+    //     after the print job has been destroyed.
+    // 6.  Wait for the one print job to be destroyed, to ensure printing
     //     finished cleanly before completing the test.
-    // 6.  The print compositor will have started to generate the document.
+    // 7.  The print compositor will have started to generate the document.
     //     Wait until that is known to have completed, to ensure printing
     //     finished cleanly before completing the test.
-    SetNumExpectedMessages(/*num=*/6);
+    SetNumExpectedMessages(/*num=*/7);
   }
 
   StartBasicPrint(web_contents);
@@ -3423,6 +3480,7 @@
 
   EXPECT_EQ(start_printing_result(), mojom::ResultCode::kFailed);
   EXPECT_EQ(error_dialog_shown_count(), 1u);
+  EXPECT_EQ(cancel_count(), 1);
   EXPECT_EQ(did_composite_completion_count(), 1);
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
diff --git a/chrome/browser/printing/print_job_manager.h b/chrome/browser/printing/print_job_manager.h
index f200926..e1b9d1c 100644
--- a/chrome/browser/printing/print_job_manager.h
+++ b/chrome/browser/printing/print_job_manager.h
@@ -35,8 +35,9 @@
   // TODO(maruel):  Have them vanish after a timeout (~5 minutes?)
   void QueuePrinterQuery(std::unique_ptr<PrinterQuery> query);
 
-  // Pops a queued PrinterQuery object that was previously queued or creates
-  // a new one. Can be called from any thread.
+  // Pops a queued PrinterQuery object that was previously queued.  Returns
+  // nullptr if there is no query matching `document_cookie`.
+  // Can be called from any thread.
   std::unique_ptr<PrinterQuery> PopPrinterQuery(int document_cookie);
 
   // Creates new query. Virtual so that tests can override it.
diff --git a/chrome/browser/printing/print_job_worker_oop.cc b/chrome/browser/printing/print_job_worker_oop.cc
index 795044e..aa2aa3b 100644
--- a/chrome/browser/printing/print_job_worker_oop.cc
+++ b/chrome/browser/printing/print_job_worker_oop.cc
@@ -170,6 +170,8 @@
                                                mojom::ResultCode result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (result != mojom::ResultCode::kSuccess) {
+    // Once an error happens during rendering, there could be multiple calls
+    // to here as the queue of sent pages all return back with error.
     PRINTER_LOG(ERROR)
         << "Error rendering printed page via service for document "
         << document_oop_->cookie() << ": " << result;
@@ -240,6 +242,17 @@
   document_oop_ = nullptr;
 }
 
+void PrintJobWorkerOop::OnDidCancel(scoped_refptr<PrintJob> job) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DVLOG(1) << "Cancel completed for printing via service for document "
+           << document_oop_->cookie();
+
+  UnregisterServiceManagerClient();
+
+  // Done with private document reference.
+  document_oop_ = nullptr;
+}
+
 #if BUILDFLAG(IS_WIN)
 bool PrintJobWorkerOop::SpoolPage(PrintedPage* page) {
   DCHECK(task_runner()->RunsTasksInCurrentSequence());
@@ -359,10 +372,12 @@
 }
 
 void PrintJobWorkerOop::OnFailure() {
+  // Retain a reference to the PrintJob to ensure it doesn't get deleted before
+  // the `OnDidCancel()` callback occurs.
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&PrintJobWorkerOop::UnregisterServiceManagerClient,
-                     ui_weak_factory_.GetWeakPtr()));
+      FROM_HERE, base::BindOnce(&PrintJobWorkerOop::SendCancel,
+                                ui_weak_factory_.GetWeakPtr(),
+                                base::WrapRefCounted(print_job())));
   PrintJobWorker::OnFailure();
 }
 
@@ -581,4 +596,29 @@
                                           printing_context()->job_id()));
 }
 
+void PrintJobWorkerOop::SendCancel(scoped_refptr<PrintJob> job) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // If an error has occurred during rendering in middle of a multi-page job,
+  // it could be possible for the `OnDidRenderPrintedPage()` callback of latter
+  // pages to still go through error processing.  In such a case the document
+  // might already have been canceled, so we should ensure to only send a
+  // cancel request to the service if we haven't already done so.
+  if (print_cancel_requested_)
+    return;
+
+  print_cancel_requested_ = true;
+  VLOG(1) << "Sending cancel for document " << document_oop_->cookie();
+
+  PrintBackendServiceManager& service_mgr =
+      PrintBackendServiceManager::GetInstance();
+
+  // Retain a reference to the PrintJob to ensure it doesn't get deleted before
+  // the `OnDidCancel()` callback occurs.
+  service_mgr.Cancel(
+      device_name_, document_oop_->cookie(),
+      base::BindOnce(&PrintJobWorkerOop::OnDidCancel,
+                     ui_weak_factory_.GetWeakPtr(), std::move(job)));
+}
+
 }  // namespace printing
diff --git a/chrome/browser/printing/print_job_worker_oop.h b/chrome/browser/printing/print_job_worker_oop.h
index 36d51600..211f6dbb 100644
--- a/chrome/browser/printing/print_job_worker_oop.h
+++ b/chrome/browser/printing/print_job_worker_oop.h
@@ -61,6 +61,7 @@
 #endif
   virtual void OnDidRenderPrintedDocument(mojom::ResultCode result);
   virtual void OnDidDocumentDone(int job_id, mojom::ResultCode result);
+  virtual void OnDidCancel(scoped_refptr<PrintJob> job);
 
   // `PrintJobWorker` overrides.
 #if BUILDFLAG(IS_WIN)
@@ -113,6 +114,7 @@
       mojom::MetafileDataType data_type,
       base::ReadOnlySharedMemoryRegion serialized_data);
   void SendDocumentDone();
+  void SendCancel(scoped_refptr<PrintJob> job);
 
   // Used to test spooling memory error handling.
   bool simulate_spooling_memory_errors_ = false;
@@ -149,6 +151,10 @@
   // Tracks if a restart for printing has already been attempted.
   bool print_retried_ = false;
 
+  // Tracks if the service has already been requested to cancel printing the
+  // document
+  bool print_cancel_requested_ = false;
+
   // Weak pointers have flags that get bound to the thread where they are
   // checked, so it is necessary to use different factories when getting a
   // weak pointer to send to the worker task runner vs. to the UI thread.
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index 3cee630..08cb184 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -256,7 +256,6 @@
     "AppLifetimeMonitor",
     "AppLoadService",
     "AppRestoreService",
-    "AppShortcutManager",
     "AppTerminationObserver",
     "AppWindowRegistry",
     "AudioAPI",
@@ -548,7 +547,9 @@
     "AppLoadService",
     "AppRestoreService",
     "AppServiceProxy",
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
     "AppShortcutManager",
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
     "AppTerminationObserver",
     "AppWindowRegistry",
     "AudioAPI",
diff --git a/chrome/browser/resources/access_code_cast/BUILD.gn b/chrome/browser/resources/access_code_cast/BUILD.gn
index 5e5a6b5..acfdc79 100644
--- a/chrome/browser/resources/access_code_cast/BUILD.gn
+++ b/chrome/browser/resources/access_code_cast/BUILD.gn
@@ -20,8 +20,8 @@
   non_web_component_files = [ "browser_proxy.ts" ]
 
   mojo_files_deps = [
-    "//chrome/browser/ui/webui/access_code_cast:mojo_bindings_webui_js",
-    "//components/media_router/common/mojom:route_request_result_code_webui_js",
+    "//chrome/browser/ui/webui/access_code_cast:mojo_bindings_js__generator",
+    "//components/media_router/common/mojom:route_request_result_code_js__generator",
   ]
   mojo_files = [
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/access_code_cast/access_code_cast.mojom-webui.js",
diff --git a/chrome/browser/resources/app_home/BUILD.gn b/chrome/browser/resources/app_home/BUILD.gn
index 9b442a8..012beeda 100644
--- a/chrome/browser/resources/app_home/BUILD.gn
+++ b/chrome/browser/resources/app_home/BUILD.gn
@@ -17,7 +17,7 @@
   non_web_component_files = [ "browser_proxy.ts" ]
 
   mojo_files_deps =
-      [ "//chrome/browser/ui/webui/app_home:mojo_bindings_webui_js" ]
+      [ "//chrome/browser/ui/webui/app_home:mojo_bindings_js__generator" ]
 
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/app_home/app_home.mojom-webui.js" ]
   ts_composite = true
diff --git a/chrome/browser/resources/app_service_internals/BUILD.gn b/chrome/browser/resources/app_service_internals/BUILD.gn
index f18751dd..baeefcd 100644
--- a/chrome/browser/resources/app_service_internals/BUILD.gn
+++ b/chrome/browser/resources/app_service_internals/BUILD.gn
@@ -11,9 +11,7 @@
 
   web_component_files = [ "app_service_internals.ts" ]
 
-  mojo_files_deps = [
-    "//chrome/browser/ui/webui/app_service_internals:mojo_bindings_webui_js",
-  ]
+  mojo_files_deps = [ "//chrome/browser/ui/webui/app_service_internals:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/app_service_internals/app_service_internals.mojom-webui.js" ]
 
   ts_deps = [ "//third_party/polymer/v3_0:library" ]
diff --git a/chrome/browser/resources/browsing_topics/BUILD.gn b/chrome/browser/resources/browsing_topics/BUILD.gn
index 6f313c6..dc5bd21a 100644
--- a/chrome/browser/resources/browsing_topics/BUILD.gn
+++ b/chrome/browser/resources/browsing_topics/BUILD.gn
@@ -14,7 +14,7 @@
   non_web_component_files = [ "browsing_topics_internals.ts" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/components/browsing_topics/mojom/browsing_topics_internals.mojom-webui.js" ]
   mojo_files_deps =
-      [ "//components/browsing_topics/mojom:mojo_bindings_webui_js" ]
+      [ "//components/browsing_topics/mojom:mojo_bindings_js__generator" ]
 
   ts_deps = [ "//ui/webui/resources:library" ]
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 345722c..822e96c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -25,7 +25,6 @@
 import {ChromeVoxBackground} from './classic_background.js';
 import {ClipboardHandler} from './clipboard_handler.js';
 import {CommandHandler} from './command_handler.js';
-import {CommandHandlerInterface} from './command_handler_interface.js';
 import {DesktopAutomationHandler} from './desktop_automation_handler.js';
 import {DesktopAutomationInterface} from './desktop_automation_interface.js';
 import {DownloadHandler} from './download_handler.js';
@@ -123,6 +122,7 @@
     BackgroundKeyboardHandler.init();
     BrailleCommandHandler.init();
     ClipboardHandler.init();
+    CommandHandler.init();
     DesktopAutomationHandler.init();
     DownloadHandler.init();
     EventStreamLogger.init();
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index 14c63229..19f6a0f3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -14,7 +14,6 @@
 import {KeyCode} from '../../common/key_code.js';
 import {RectUtil} from '../../common/rect_util.js';
 import {Earcon} from '../common/abstract_earcons.js';
-import {AbstractTts} from '../common/abstract_tts.js';
 import {NavBraille} from '../common/braille/nav_braille.js';
 import {BridgeConstants} from '../common/bridge_constants.js';
 import {BridgeHelper} from '../common/bridge_helper.js';
@@ -27,7 +26,7 @@
 import {Msgs} from '../common/msgs.js';
 import {PanelCommand, PanelCommandType} from '../common/panel_command.js';
 import {TreeDumper} from '../common/tree_dumper.js';
-import {QueueMode, TtsSpeechProperties} from '../common/tts_types.js';
+import {Personality, QueueMode, TtsSettings, TtsSpeechProperties} from '../common/tts_types.js';
 
 import {AutoScrollHandler} from './auto_scroll_handler.js';
 import {BrailleBackground} from './braille/braille_background.js';
@@ -83,7 +82,30 @@
     this.languageLoggingEnabled_ = false;
 
     SmartStickyMode.init();
-    this.init();
+    this.init_();
+  }
+
+  /** @private */
+  init_() {
+    chrome.commandLinePrivate.hasSwitch(
+        'enable-experimental-accessibility-language-detection', enabled => {
+          if (enabled) {
+            this.languageLoggingEnabled_ = true;
+          }
+        });
+    chrome.commandLinePrivate.hasSwitch(
+        'enable-experimental-accessibility-language-detection-dynamic',
+        enabled => {
+          if (enabled) {
+            this.languageLoggingEnabled_ = true;
+          }
+        });
+
+    chrome.chromeosInfoPrivate.get(['sessionType'], result => {
+      /** @type {boolean} */
+      this.isKioskSession_ = result['sessionType'] ===
+          chrome.chromeosInfoPrivate.SessionType.KIOSK;
+    });
   }
 
   /** @override */
@@ -130,22 +152,22 @@
             root => LogStore.instance.writeTreeLog(new TreeDumper(root)));
         break;
       case Command.DECREASE_TTS_RATE:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.RATE, false);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.RATE, false);
         return false;
       case Command.INCREASE_TTS_RATE:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.RATE, true);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.RATE, true);
         return false;
       case Command.DECREASE_TTS_PITCH:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.PITCH, false);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.PITCH, false);
         return false;
       case Command.INCREASE_TTS_PITCH:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.PITCH, true);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.PITCH, true);
         return false;
       case Command.DECREASE_TTS_VOLUME:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.VOLUME, false);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.VOLUME, false);
         return false;
       case Command.INCREASE_TTS_VOLUME:
-        this.increaseOrDecreaseSpeechProperty_(AbstractTts.VOLUME, true);
+        this.increaseOrDecreaseSpeechProperty_(TtsSettings.VOLUME, true);
         return false;
       case Command.STOP_SPEECH:
         ChromeVox.tts.stop();
@@ -1110,8 +1132,7 @@
         announce = Msgs.getMsg('none_echo');
         break;
     }
-    ChromeVox.tts.speak(
-        announce, QueueMode.FLUSH, AbstractTts.PERSONALITY_ANNOTATION);
+    ChromeVox.tts.speak(announce, QueueMode.FLUSH, Personality.ANNOTATION);
   }
 
   /** @private */
@@ -1758,8 +1779,7 @@
     ChromeVox.earcons.enabled = !ChromeVox.earcons.enabled;
     const announce = ChromeVox.earcons.enabled ? Msgs.getMsg('earcons_on') :
                                                  Msgs.getMsg('earcons_off');
-    ChromeVox.tts.speak(
-        announce, QueueMode.FLUSH, AbstractTts.PERSONALITY_ANNOTATION);
+    ChromeVox.tts.speak(announce, QueueMode.FLUSH, Personality.ANNOTATION);
   }
 
   /** @private */
@@ -1836,40 +1856,20 @@
   /**
    * Performs global initialization.
    */
-  init() {
-    ChromeVoxKbHandler.commandHandler = command => this.onCommand(command);
+  static init() {
+    CommandHandlerInterface.instance = new CommandHandler();
+    ChromeVoxKbHandler.commandHandler = command =>
+        CommandHandlerInterface.instance.onCommand(command);
 
-    chrome.commandLinePrivate.hasSwitch(
-        'enable-experimental-accessibility-language-detection', enabled => {
-          if (enabled) {
-            this.languageLoggingEnabled_ = true;
+    BridgeHelper.registerHandler(
+        BridgeConstants.CommandHandler.TARGET,
+        BridgeConstants.CommandHandler.Action.ON_COMMAND, command => {
+          if (Object.values(Command).includes(command)) {
+            CommandHandlerInterface.instance.onCommand(
+                /** @type {Command} */ (command));
+          } else {
+            console.warn('ChromeVox got an unrecognized command: ' + command);
           }
         });
-    chrome.commandLinePrivate.hasSwitch(
-        'enable-experimental-accessibility-language-detection-dynamic',
-        enabled => {
-          if (enabled) {
-            this.languageLoggingEnabled_ = true;
-          }
-        });
-
-    chrome.chromeosInfoPrivate.get(['sessionType'], result => {
-      /** @type {boolean} */
-      this.isKioskSession_ = result['sessionType'] ===
-          chrome.chromeosInfoPrivate.SessionType.KIOSK;
-    });
   }
 }
-
-CommandHandlerInterface.instance = new CommandHandler();
-
-BridgeHelper.registerHandler(
-    BridgeConstants.CommandHandler.TARGET,
-    BridgeConstants.CommandHandler.Action.ON_COMMAND, command => {
-      if (Object.values(Command).includes(command)) {
-        CommandHandlerInterface.instance.onCommand(
-            /** @type {Command} */ (command));
-      } else {
-        console.warn('ChromeVox got an unrecognized command: ' + command);
-      }
-    });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base.js
index 8d489e4..38c4f6c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base.js
@@ -13,10 +13,9 @@
  * extended to override methods that extract lines for multiline fields
  * or to provide other customizations.
  */
-import {AbstractTts} from '../../common/abstract_tts.js';
 import {Msgs} from '../../common/msgs.js';
 import {TtsInterface} from '../../common/tts_interface.js';
-import {QueueMode, TtsCategory, TtsSpeechProperties} from '../../common/tts_types.js';
+import {Personality, QueueMode, TtsCategory, TtsSpeechProperties} from '../../common/tts_types.js';
 import {ChromeVoxState} from '../chromevox_state.js';
 
 /**
@@ -335,19 +334,11 @@
         this.speak(lineValue, evt.triggeredByUser);
       } else if (this.start === evt.start + 1 || this.start === evt.start - 1) {
         // Moved by one character; read it.
-        if (!ChromeVoxEditableTextBase.useIBeamCursor) {
-          if (evt.start === this.value.length) {
-            this.speak(Msgs.getMsg('end_of_text_verbose'), evt.triggeredByUser);
-          } else {
-            this.speak(
-                this.value.substr(evt.start, 1), evt.triggeredByUser,
-                new TtsSpeechProperties(
-                    {'phoneticCharacters': evt.triggeredByUser}));
-          }
+        if (evt.start === this.value.length) {
+          this.speak(Msgs.getMsg('end_of_text_verbose'), evt.triggeredByUser);
         } else {
           this.speak(
-              this.value.substr(Math.min(this.start, evt.start), 1),
-              evt.triggeredByUser,
+              this.value.substr(evt.start, 1), evt.triggeredByUser,
               new TtsSpeechProperties(
                   {'phoneticCharacters': evt.triggeredByUser}));
         }
@@ -415,7 +406,7 @@
   describeTextChanged(prev, evt) {
     let personality = new TtsSpeechProperties();
     if (evt.value.length < (prev.value.length - 1)) {
-      personality = AbstractTts.PERSONALITY_DELETED;
+      personality = Personality.DELETED;
     }
     if (this.isPassword) {
       this.speak(
@@ -469,8 +460,7 @@
       // Forward deletions causes reading of the character immediately to the
       // right of the caret or the deleted text depending on the iBeam cursor
       // setting.
-      if (prev.start === evt.start && prev.end === evt.end &&
-          !ChromeVoxEditableTextBase.useIBeamCursor) {
+      if (prev.start === evt.start && prev.end === evt.end) {
         this.speak(evt.value[evt.start], evt.triggeredByUser);
       } else {
         this.describeTextChangedHelper(
@@ -613,8 +603,8 @@
       utterance = deleted + ', deleted';
     } else if (deletedLen === 1) {
       utterance = deleted;
-      // Single-deleted characters should also use PERSONALITY_DELETED.
-      opt_personality = AbstractTts.PERSONALITY_DELETED;
+      // Single-deleted characters should also use Personality.DELETED.
+      opt_personality = Personality.DELETED;
     }
 
     if (autocompleteSuffix && utterance) {
@@ -693,27 +683,6 @@
   }
 }
 
-
-/**
- * Whether or not moving the cursor from one character to another considers
- * the cursor to be a block (false) or an i-beam (true).
- *
- * If the cursor is a block, then the value of the character to the right
- * of the cursor index is always read when the cursor moves, no matter what
- * the previous cursor location was - this is how PC screenreaders work.
- *
- * If the cursor is an i-beam, moving the cursor by one character reads the
- * character that was crossed over, which may be the character to the left or
- * right of the new cursor index depending on the direction.
- *
- * If the current platform is a Mac, we will use an i-beam cursor. If not,
- * then we will use the block cursor.
- *
- * @type {boolean}
- */
-ChromeVoxEditableTextBase.useIBeamCursor =
-    localStorage['useIBeamCursor'] === String(true);
-
 /**
  * @type {boolean} Whether insertions (i.e. changes of greater than one
  * character) should be spoken.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
index 743b7c3..a97d6ef6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
@@ -95,11 +95,7 @@
     await importModule(
         ['ChromeVoxEditableTextBase', 'TextChangedEvent', 'TypingEcho'],
         '/chromevox/background/editing/editable_text_base.js');
-    await importModule('AbstractTts', '/chromevox/common/abstract_tts.js');
 
-    // TODO: These tests are all assuming we used the IBeam cursor.
-    // We need to add coverage for block cursor.
-    ChromeVoxEditableTextBase.useIBeamCursor = true;
     ChromeVoxState.instance.typingEcho = TypingEcho.CHARACTER_AND_WORD;
     ChromeVoxEditableTextBase.eventTypingEcho = false;
     ChromeVoxEditableTextBase.shouldSpeakInsertions = true;
@@ -184,7 +180,6 @@
   assertEqualStringArrays(['.', 'd', 'l'], tts.get());
 
   // Forward-delete
-  ChromeVoxEditableTextBase.useIBeamCursor = true;
   obj.changed(new TextChangeEvent('Hello, Wor', 9, 9));
   obj.changed(new TextChangeEvent('Hello, Wor', 8, 8));
   obj.changed(new TextChangeEvent('Hello, Wor', 7, 7));
@@ -194,7 +189,6 @@
   assertEqualStringArrays(['r', 'o', 'W', 'W', 'o', 'r'], tts.get());
 
   obj.changed(new TextChangeEvent('Hello, Wor', 10, 10));
-  ChromeVoxEditableTextBase.useIBeamCursor = false;
   obj.changed(new TextChangeEvent('Hello, Wor', 9, 9));
   obj.changed(new TextChangeEvent('Hello, Wor', 8, 8));
   obj.changed(new TextChangeEvent('Hello, Wor', 7, 7));
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
index 7967c70..4cf776f9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
@@ -11,12 +11,11 @@
 import {constants} from '../../../common/constants.js';
 import {Cursor, CursorMovement, CursorUnit} from '../../../common/cursors/cursor.js';
 import {CursorRange} from '../../../common/cursors/range.js';
-import {AbstractTts} from '../../common/abstract_tts.js';
 import {NavBraille} from '../../common/braille/nav_braille.js';
 import {ChromeVoxEvent} from '../../common/custom_automation_event.js';
 import {Msgs} from '../../common/msgs.js';
 import {MultiSpannable, Spannable} from '../../common/spannable.js';
-import {QueueMode} from '../../common/tts_types.js';
+import {Personality, QueueMode} from '../../common/tts_types.js';
 import {BrailleBackground} from '../braille/braille_background.js';
 import {LibLouis} from '../braille/liblouis.js';
 import {BrailleTextStyleSpan, ValueSelectionSpan, ValueSpan} from '../braille/spans.js';
@@ -835,8 +834,7 @@
     if (msgs.length) {
       msgs.forEach(msg => {
         ChromeVox.tts.speak(
-            Msgs.getMsg(msg), QueueMode.QUEUE,
-            AbstractTts.PERSONALITY_ANNOTATION);
+            Msgs.getMsg(msg), QueueMode.QUEUE, Personality.ANNOTATION);
       });
     }
   }
@@ -906,7 +904,7 @@
       msgs.forEach(msgObject => {
         ChromeVox.tts.speak(
             Msgs.getMsg(msgObject.msg, msgObject.opt_subs), QueueMode.QUEUE,
-            AbstractTts.PERSONALITY_ANNOTATION);
+            Personality.ANNOTATION);
       });
     }
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
index be62bc5..56babc2c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
@@ -6,10 +6,10 @@
  * @fileoverview Common page for reading and writing preferences from
  * the background context (background page or options page).
  */
-import {AbstractTts} from '../common/abstract_tts.js';
 import {BridgeConstants} from '../common/bridge_constants.js';
 import {BridgeHelper} from '../common/bridge_helper.js';
 import {Msgs} from '../common/msgs.js';
+import {Personality} from '../common/tts_types.js';
 
 import {ChromeVox} from './chromevox.js';
 import {ConsoleTts} from './console_tts.js';
@@ -129,7 +129,7 @@
   setAndAnnounceStickyPref(value) {
     chrome.accessibilityPrivate.setKeyboardListener(true, value);
     new Output()
-        .withInitialSpeechProperties(AbstractTts.PERSONALITY_ANNOTATION)
+        .withInitialSpeechProperties(Personality.ANNOTATION)
         .withString(
             value ? Msgs.getMsg('sticky_mode_enabled') :
                     Msgs.getMsg('sticky_mode_disabled'))
@@ -182,7 +182,6 @@
   'speakTextUnderMouse': false,
   'sticky': false,
   'typingEcho': 0,
-  'useIBeamCursor': false,
   'useClassic': false,
   'usePitchChanges': true,
   'useVerboseMode': true,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
index 126ee7e..c1e11eb 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
@@ -34,7 +34,7 @@
     await super.setUpDeferred();
 
     // Alphabetical based on file path.
-    await importModule('AbstractTts', '/chromevox/common/abstract_tts.js');
+    await importModule('TtsSettings', '/chromevox/common/tts_types.js');
   }
 };
 
@@ -45,9 +45,9 @@
       const mockFeedback = this.createMockFeedback();
       await this.runWithLoadedTree(`unused`);
       const increaseRate = realTts.increaseOrDecreaseProperty.bind(
-          realTts, AbstractTts.RATE, true);
+          realTts, TtsSettings.RATE, true);
       const decreaseRate = realTts.increaseOrDecreaseProperty.bind(
-          realTts, AbstractTts.RATE, false);
+          realTts, TtsSettings.RATE, false);
 
       mockFeedback.call(doCmd('showTtsSettings'))
           .expectSpeech(
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background.js
index bd02ef4c..eac102af 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background.js
@@ -8,7 +8,6 @@
  */
 
 import {constants} from '../../common/constants.js';
-import {AbstractTts} from '../common/abstract_tts.js';
 import {BridgeConstants} from '../common/bridge_constants.js';
 import {BridgeHelper} from '../common/bridge_helper.js';
 import {CompositeTts} from '../common/composite_tts.js';
@@ -16,7 +15,7 @@
 import {PanelCommand, PanelCommandType} from '../common/panel_command.js';
 import {ChromeTtsBase} from '../common/tts_base.js';
 import {TtsCapturingEventListener, TtsInterface} from '../common/tts_interface.js';
-import {QueueMode, TtsSpeechProperties} from '../common/tts_types.js';
+import * as ttsTypes from '../common/tts_types.js';
 
 import {ChromeVox} from './chromevox.js';
 import {ConsoleTts} from './console_tts.js';
@@ -117,7 +116,7 @@
 
     /** @private {number} */
     this.currentPunctuationEcho_ =
-        parseInt(localStorage[AbstractTts.PUNCTUATION_ECHO] || 1, 10);
+        parseInt(localStorage[ttsTypes.TtsSettings.PUNCTUATION_ECHO] || 1, 10);
 
     /**
      * A list of punctuation characters that should always be spliced into
@@ -218,9 +217,9 @@
 
   /**
    * @param {string} textString The string of text to be spoken.
-   * @param {QueueMode} queueMode The queue mode to use for speaking.
-   * @param {TtsSpeechProperties=} properties Speech properties to use for
-   *     this utterance.
+   * @param {ttsTypes.QueueMode} queueMode The queue mode to use for speaking.
+   * @param {ttsTypes.TtsSpeechProperties=} properties Speech properties to use
+   *     for this utterance.
    * @return {TtsInterface} A tts object useful for chaining speak calls.
    * @override
    */
@@ -231,12 +230,12 @@
     // original value for functions that may need it.
     const originalTextString = textString;
 
-    if (this.ttsProperties[AbstractTts.VOLUME] === 0) {
+    if (this.ttsProperties[ttsTypes.TtsSettings.VOLUME] === 0) {
       return this;
     }
 
     if (!properties) {
-      properties = new TtsSpeechProperties();
+      properties = new ttsTypes.TtsSpeechProperties();
     }
 
     if (textString.length > constants.OBJECT_MAX_CHARCOUNT) {
@@ -270,7 +269,7 @@
         } catch (e) {
         }
       }
-      if (queueMode === QueueMode.FLUSH) {
+      if (queueMode === ttsTypes.QueueMode.FLUSH) {
         this.stop();
       }
       return this;
@@ -299,16 +298,16 @@
    * Split the given textString into smaller chunks and call this.speak() for
    * each chunks.
    * @param {string} textString The string of text to be spoken.
-   * @param {QueueMode} queueMode The queue mode to use for speaking.
-   * @param {TtsSpeechProperties=} properties Speech properties to use for
-   *     this utterance.
+   * @param {ttsTypes.QueueMode} queueMode The queue mode to use for speaking.
+   * @param {ttsTypes.TtsSpeechProperties=} properties Speech properties to use
+   *     for this utterance.
    * @private
    */
   speakSplittingText_(textString, queueMode, properties) {
     const chunks = PrimaryTts.splitUntilSmall(textString, '\n\r ');
     for (const chunk of chunks) {
       this.speak(chunk, queueMode, properties);
-      queueMode = QueueMode.QUEUE;
+      queueMode = ttsTypes.QueueMode.QUEUE;
     }
   }
 
@@ -362,9 +361,9 @@
     // First, take care of removing the current utterance and flushing
     // anything from the queue we need to. If we remove the current utterance,
     // make a note that we're going to stop speech.
-    if (queueMode === QueueMode.FLUSH ||
-        queueMode === QueueMode.CATEGORY_FLUSH ||
-        queueMode === QueueMode.INTERJECT) {
+    if (queueMode === ttsTypes.QueueMode.FLUSH ||
+        queueMode === ttsTypes.QueueMode.CATEGORY_FLUSH ||
+        queueMode === ttsTypes.QueueMode.INTERJECT) {
       (new PanelCommand(PanelCommandType.CLEAR_SPEECH)).send();
 
       if (this.shouldCancel_(this.currentUtterance_, utterance)) {
@@ -385,7 +384,7 @@
     }
 
     // Now, some special handling for interjections.
-    if (queueMode === QueueMode.INTERJECT) {
+    if (queueMode === ttsTypes.QueueMode.INTERJECT) {
       // Move all utterances to a secondary queue to be restored later.
       this.utteranceQueueInterruptedByInterjection_ = this.utteranceQueue_;
 
@@ -405,7 +404,7 @@
       setTimeout(() => {
         // Utterances on the current queue are now also interjections.
         for (let i = 0; i < this.utteranceQueue_.length; i++) {
-          this.utteranceQueue_[i].queueMode = QueueMode.INTERJECT;
+          this.utteranceQueue_[i].queueMode = ttsTypes.QueueMode.INTERJECT;
         }
         this.utteranceQueue_ = this.utteranceQueue_.concat(
             this.utteranceQueueInterruptedByInterjection_);
@@ -567,13 +566,13 @@
       return false;
     }
     switch (newUtterance.queueMode) {
-      case QueueMode.QUEUE:
+      case ttsTypes.QueueMode.QUEUE:
         return false;
-      case QueueMode.INTERJECT:
-        return utteranceToCancel.queueMode === QueueMode.INTERJECT;
-      case QueueMode.FLUSH:
+      case ttsTypes.QueueMode.INTERJECT:
+        return utteranceToCancel.queueMode === ttsTypes.QueueMode.INTERJECT;
+      case ttsTypes.QueueMode.FLUSH:
         return true;
-      case QueueMode.CATEGORY_FLUSH:
+      case ttsTypes.QueueMode.CATEGORY_FLUSH:
         return (
             utteranceToCancel.properties['category'] ===
             newUtterance.properties['category']);
@@ -602,13 +601,13 @@
 
     let pref;
     switch (propertyName) {
-      case AbstractTts.RATE:
+      case ttsTypes.TtsSettings.RATE:
         pref = 'settings.tts.speech_rate';
         break;
-      case AbstractTts.PITCH:
+      case ttsTypes.TtsSettings.PITCH:
         pref = 'settings.tts.speech_pitch';
         break;
-      case AbstractTts.VOLUME:
+      case ttsTypes.TtsSettings.VOLUME:
         pref = 'settings.tts.speech_volume';
         break;
       default:
@@ -685,14 +684,15 @@
 
     // Perform any remaining processing such as punctuation expansion.
     let punctEcho = null;
-    if (properties[AbstractTts.PUNCTUATION_ECHO]) {
-      for (let i = 0; punctEcho = AbstractTts.PUNCTUATION_ECHOES[i]; i++) {
-        if (properties[AbstractTts.PUNCTUATION_ECHO] === punctEcho.name) {
+    if (properties[ttsTypes.TtsSettings.PUNCTUATION_ECHO]) {
+      for (let i = 0; punctEcho = ttsTypes.PunctuationEchoes[i]; i++) {
+        if (properties[ttsTypes.TtsSettings.PUNCTUATION_ECHO] ===
+            punctEcho.name) {
           break;
         }
       }
     } else {
-      punctEcho = AbstractTts.PUNCTUATION_ECHOES[this.currentPunctuationEcho_];
+      punctEcho = ttsTypes.PunctuationEchoes[this.currentPunctuationEcho_];
     }
     text = text.replace(
         punctEcho.regexp, this.createPunctuationReplace_(punctEcho.clear));
@@ -718,12 +718,12 @@
 
   /** @override */
   toggleSpeechOnOrOff() {
-    const previousValue = this.ttsProperties[AbstractTts.VOLUME];
+    const previousValue = this.ttsProperties[ttsTypes.TtsSettings.VOLUME];
     const toggle = () => {
       if (previousValue === 0) {
-        this.ttsProperties[AbstractTts.VOLUME] = 1;
+        this.ttsProperties[ttsTypes.TtsSettings.VOLUME] = 1;
       } else {
-        this.ttsProperties[AbstractTts.VOLUME] = 0;
+        this.ttsProperties[ttsTypes.TtsSettings.VOLUME] = 0;
         this.stop();
       }
     };
@@ -743,11 +743,11 @@
    * Method that updates the punctuation echo level, and also persists setting
    * to local storage.
    * @param {number} punctuationEcho The index of the desired punctuation echo
-   * level in AbstractTts.PUNCTUATION_ECHOES.
+   * level in ttsTypes.PunctuationEchoes.
    */
   updatePunctuationEcho(punctuationEcho) {
     this.currentPunctuationEcho_ = punctuationEcho;
-    localStorage[AbstractTts.PUNCTUATION_ECHO] = punctuationEcho;
+    localStorage[ttsTypes.TtsSettings.PUNCTUATION_ECHO] = punctuationEcho;
   }
 
   /**
@@ -756,9 +756,8 @@
    */
   cyclePunctuationEcho() {
     this.updatePunctuationEcho(
-        (this.currentPunctuationEcho_ + 1) %
-        AbstractTts.PUNCTUATION_ECHOES.length);
-    return AbstractTts.PUNCTUATION_ECHOES[this.currentPunctuationEcho_].msg;
+        (this.currentPunctuationEcho_ + 1) % ttsTypes.PunctuationEchoes.length);
+    return ttsTypes.PunctuationEchoes[this.currentPunctuationEcho_].msg;
   }
 
   /**
@@ -791,7 +790,7 @@
       return clear ? retain :
                      ' ' +
               (new goog.i18n.MessageFormat(
-                   Msgs.getMsg(AbstractTts.CHARACTER_DICTIONARY[match])))
+                   Msgs.getMsg(ttsTypes.CharacterDictionary[match])))
                   .format({'COUNT': 1}) +
               retain + ' ';
     };
@@ -800,8 +799,8 @@
   /**
    * Queues phonetic disambiguation for characters if disambiguation is found.
    * @param {string} text The text for which we want to get phonetic data.
-   * @param {!TtsSpeechProperties} properties Speech properties to use for
-   *     this utterance.
+   * @param {!ttsTypes.TtsSpeechProperties} properties Speech properties to use
+   *     for this utterance.
    * @private
    */
   pronouncePhonetically_(text, properties) {
@@ -827,7 +826,7 @@
     const phoneticText = PhoneticData.forCharacter(text, properties.lang);
     if (phoneticText) {
       properties.delay = true;
-      this.speak(phoneticText, QueueMode.QUEUE, properties);
+      this.speak(phoneticText, ttsTypes.QueueMode.QUEUE, properties);
     }
   }
 
@@ -880,16 +879,16 @@
       let propertyName;
       switch (pref.key) {
         case 'settings.tts.speech_rate':
-          propertyName = AbstractTts.RATE;
+          propertyName = ttsTypes.TtsSettings.RATE;
           msg = 'announce_rate';
           this.setHintDelayMS(/** @type {number} */ (pref.value));
           break;
         case 'settings.tts.speech_pitch':
-          propertyName = AbstractTts.PITCH;
+          propertyName = ttsTypes.TtsSettings.PITCH;
           msg = 'announce_pitch';
           break;
         case 'settings.tts.speech_volume':
-          propertyName = AbstractTts.VOLUME;
+          propertyName = ttsTypes.TtsSettings.VOLUME;
           msg = 'announce_volume';
           break;
         default:
@@ -906,7 +905,8 @@
           Math.round(this.propertyToPercentage(propertyName) * 100);
       const announcement = Msgs.getMsg(msg, [valueAsPercent]);
       ChromeVox.tts.speak(
-          announcement, QueueMode.FLUSH, AbstractTts.PERSONALITY_ANNOTATION);
+          announcement, ttsTypes.QueueMode.FLUSH,
+          ttsTypes.Personality.ANNOTATION);
     });
   }
 
@@ -924,18 +924,19 @@
     this.updateVoice_('', () => {
       // Ensure this announcement doesn't get cut off by speech triggered by
       // updateFromPrefs_().
-      // Copy properties from AbstractTts.PERSONALITY_ANNOTATION and add the
+      // Copy properties from ttsTypes.Personality.ANNOTATION and add the
       // doNotInterrupt property.
       const speechProperties = {};
-      const sourceProperties = AbstractTts.PERSONALITY_ANNOTATION || {};
+      const sourceProperties = ttsTypes.Personality.ANNOTATION || {};
       for (const [key, value] of Object.entries(sourceProperties)) {
         speechProperties[key] = value;
       }
       speechProperties['doNotInterrupt'] = true;
 
       ChromeVox.tts.speak(
-          Msgs.getMsg('announce_tts_default_settings'), QueueMode.FLUSH,
-          new TtsSpeechProperties(speechProperties));
+          Msgs.getMsg('announce_tts_default_settings'),
+          ttsTypes.QueueMode.FLUSH,
+          new ttsTypes.TtsSpeechProperties(speechProperties));
     });
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
index 0f534bc..4a1cb82 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
@@ -9,7 +9,7 @@
 
 import {Msgs} from './msgs.js';
 import {TtsInterface} from './tts_interface.js';
-import {QueueMode, TtsSpeechProperties} from './tts_types.js';
+import * as ttsTypes from './tts_types.js';
 
 /**
  * Creates a new instance.
@@ -61,7 +61,7 @@
       // Create an expression that matches all words in the substitution
       // dictionary.
       const symbols = [];
-      for (const symbol in AbstractTts.SUBSTITUTION_DICTIONARY) {
+      for (const symbol in ttsTypes.SubstitutionDictionary) {
         symbols.push(symbol);
       }
       const expr = '(' + symbols.join('|') + ')';
@@ -71,8 +71,8 @@
 
   /**
    * @param {string} textString
-   * @param {QueueMode} queueMode
-   * @param {TtsSpeechProperties=} properties
+   * @param {ttsTypes.QueueMode} queueMode
+   * @param {ttsTypes.TtsSpeechProperties=} properties
    * @override
    */
   speak(textString, queueMode, properties) {
@@ -130,7 +130,7 @@
       }
     }
     if (properties) {
-      const tts = AbstractTts;
+      const tts = ttsTypes.TtsSettings;
       if (typeof (properties[tts.VOLUME]) === 'number') {
         mergedProperties[tts.VOLUME] = properties[tts.VOLUME];
       }
@@ -195,10 +195,10 @@
       if (localStorage['capitalStrategy'] === 'increasePitch') {
         // Closure doesn't allow the use of for..in or [] with structs, so
         // convert to a pure JSON object.
-        const PERSONALITY_CAPITAL = AbstractTts.PERSONALITY_CAPITAL.toJSON();
-        for (const prop in PERSONALITY_CAPITAL) {
+        const CAPITAL = ttsTypes.Personality.CAPITAL.toJSON();
+        for (const prop in CAPITAL) {
           if (properties[prop] === undefined) {
-            properties[prop] = PERSONALITY_CAPITAL[prop];
+            properties[prop] = CAPITAL[prop];
           }
         }
       } else if (localStorage['capitalStrategy'] === 'announceCapitals') {
@@ -226,14 +226,14 @@
     // simultaneously.
     text = text.replace(
         AbstractTts.substitutionDictionaryRegexp_, function(symbol) {
-          return ' ' + AbstractTts.SUBSTITUTION_DICTIONARY[symbol] + ' ';
+          return ' ' + ttsTypes.SubstitutionDictionary[symbol] + ' ';
         });
 
     // Handle single characters that we want to make sure we pronounce.
     if (text.length === 1) {
-      return AbstractTts.CHARACTER_DICTIONARY[text] ?
+      return ttsTypes.CharacterDictionary[text] ?
           (new goog.i18n.MessageFormat(
-               Msgs.getMsg(AbstractTts.CHARACTER_DICTIONARY[text])))
+               Msgs.getMsg(ttsTypes.CharacterDictionary[text])))
               .format({'COUNT': 1}) :
           text.toUpperCase();
     }
@@ -256,7 +256,7 @@
     const count = match.length;
     return ' ' +
         (new goog.i18n.MessageFormat(
-             Msgs.getMsg(AbstractTts.CHARACTER_DICTIONARY[match[0]])))
+             Msgs.getMsg(ttsTypes.CharacterDictionary[match[0]])))
             .format({'COUNT': count}) +
         ' ';
   }
@@ -281,7 +281,6 @@
   }
 }
 
-
 /**
  * Default TTS properties for this TTS engine.
  * @type {Object}
@@ -289,210 +288,6 @@
  */
 AbstractTts.prototype.ttsProperties;
 
-
-/** TTS rate property. @type {string} */
-AbstractTts.RATE = 'rate';
-/** TTS pitch property. @type {string} */
-AbstractTts.PITCH = 'pitch';
-/** TTS volume property. @type {string} */
-AbstractTts.VOLUME = 'volume';
-/** TTS language property. @type {string} */
-AbstractTts.LANG = 'lang';
-
-/** TTS relative rate property. @type {string} */
-AbstractTts.RELATIVE_RATE = 'relativeRate';
-/** TTS relative pitch property. @type {string} */
-AbstractTts.RELATIVE_PITCH = 'relativePitch';
-/** TTS relative volume property. @type {string} */
-AbstractTts.RELATIVE_VOLUME = 'relativeVolume';
-
-/** TTS color property (for the lens display). @type {string} */
-AbstractTts.COLOR = 'color';
-/** TTS CSS font-weight property (for the lens display). @type {string} */
-AbstractTts.FONT_WEIGHT = 'fontWeight';
-
-/** TTS punctuation-echo property. @type {string} */
-AbstractTts.PUNCTUATION_ECHO = 'punctuationEcho';
-
-/**
- * List of punctuation echoes that the user can cycle through.
- * @type {!Array<{name:(string),
- * msg:(string),
- * regexp:(RegExp),
- * clear:(boolean)}>}
- */
-AbstractTts.PUNCTUATION_ECHOES = [
-  // Punctuation echoed for the 'none' option.
-  {
-    name: 'none',
-    msg: 'no_punctuation',
-    regexp: /[-$#"()*;:<>\n\\\/+='~`@_]/g,
-    clear: true,
-  },
-
-  // Punctuation echoed for the 'some' option.
-  {
-    name: 'some',
-    msg: 'some_punctuation',
-    regexp: /[$#"*<>\\\/\{\}+=~`%\u2022\u25e6\u25a0]/g,
-    clear: false,
-  },
-
-  // Punctuation echoed for the 'all' option.
-  {
-    name: 'all',
-    msg: 'all_punctuation',
-    regexp: /[-$#"()*;:<>\n\\\/\{\}\[\]+='~`!@_.,?%\u2022\u25e6\u25a0]/g,
-    clear: false,
-  },
-];
-
-/** TTS pause property. @type {string} */
-AbstractTts.PAUSE = 'pause';
-
-/**
- * TTS personality for annotations - text spoken by ChromeVox that
- * elaborates on a user interface element but isn't displayed on-screen.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_ANNOTATION = new TtsSpeechProperties({
-  'relativePitch': -0.25,
-  // TODO:(rshearer) Added this color change for I/O presentation.
-  'color': 'yellow',
-  'punctuationEcho': 'none',
-});
-
-
-/**
- * TTS personality for announcements - text spoken by ChromeVox that
- * isn't tied to any user interface elements.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_ANNOUNCEMENT = new TtsSpeechProperties({
-  'punctuationEcho': 'none',
-});
-
-/**
- * TTS personality for alerts from the system, such as battery level
- * warnings.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_SYSTEM_ALERT = new TtsSpeechProperties({
-  'punctuationEcho': 'none',
-  'doNotInterrupt': true,
-});
-
-/**
- * TTS personality for an aside - text in parentheses.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_ASIDE = new TtsSpeechProperties({
-  'relativePitch': -0.1,
-  'color': '#669',
-});
-
-/**
- * TTS personality for capital letters.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_CAPITAL = new TtsSpeechProperties({
-  'relativePitch': 0.2,
-});
-
-/**
- * TTS personality for deleted text.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_DELETED = new TtsSpeechProperties({
-  'punctuationEcho': 'none',
-  'relativePitch': -0.6,
-});
-
-/**
- * TTS personality for quoted text.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_QUOTE = new TtsSpeechProperties({
-  'relativePitch': 0.1,
-  'color': '#b6b',
-  'fontWeight': 'bold',
-});
-
-/**
- * TTS personality for strong or bold text.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_STRONG = new TtsSpeechProperties({
-  'relativePitch': 0.1,
-  'color': '#b66',
-  'fontWeight': 'bold',
-});
-
-/**
- * TTS personality for emphasis or italicized text.
- * @type {!TtsSpeechProperties}
- */
-AbstractTts.PERSONALITY_EMPHASIS = new TtsSpeechProperties({
-  'relativeVolume': 0.1,
-  'relativeRate': -0.1,
-  'color': '#6bb',
-  'fontWeight': 'bold',
-});
-
-/**
- * Flag indicating if the TTS is being debugged.
- * @type {boolean}
- */
-AbstractTts.DEBUG = true;
-
-/**
- * Character dictionary. These symbols are replaced with their human readable
- * equivalents. This replacement only occurs for single character utterances.
- * @type {Object<string>}
- */
-AbstractTts.CHARACTER_DICTIONARY = {
-  ' ': 'space',
-  '\u00a0': 'space',
-  '`': 'backtick',
-  '~': 'tilde',
-  '!': 'exclamation',
-  '@': 'at',
-  '#': 'pound',
-  '$': 'dollar',
-  '%': 'percent',
-  '^': 'caret',
-  '&': 'ampersand',
-  '*': 'asterisk',
-  '(': 'open_paren',
-  ')': 'close_paren',
-  '-': 'dash',
-  '_': 'underscore',
-  '=': 'equals',
-  '+': 'plus',
-  '[': 'left_bracket',
-  ']': 'right_bracket',
-  '{': 'left_brace',
-  '}': 'right_brace',
-  '|': 'pipe',
-  ';': 'semicolon',
-  ':': 'colon',
-  ',': 'comma',
-  '.': 'dot',
-  '<': 'less_than',
-  '>': 'greater_than',
-  '/': 'slash',
-  '?': 'question_mark',
-  '"': 'quote',
-  '\'': 'apostrophe',
-  '\t': 'tab',
-  '\r': 'return',
-  '\n': 'new_line',
-  '\\': 'backslash',
-  '\u2022': 'bullet',
-  '\u25e6': 'white_bullet',
-  '\u25a0': 'square_bullet',
-};
-
 /**
  * Pronunciation dictionary regexp.
  * @private {RegExp}
@@ -500,58 +295,6 @@
 AbstractTts.pronunciationDictionaryRegexp_;
 
 /**
- * Substitution dictionary. These symbols or patterns are ALWAYS substituted
- * whenever they occur, so this should be reserved only for unicode characters
- * and characters that never have any different meaning in context.
- *
- * For example, do not include '$' here because $2 should be read as
- * "two dollars".
- * @type {Object<string>}
- */
-AbstractTts.SUBSTITUTION_DICTIONARY = {
-  '://': 'colon slash slash',
-  '\u00bc': 'one fourth',
-  '\u00bd': 'one half',
-  '\u2190': 'left arrow',
-  '\u2191': 'up arrow',
-  '\u2192': 'right arrow',
-  '\u2193': 'down arrow',
-  '\u21d0': 'left double arrow',
-  '\u21d1': 'up double arrow',
-  '\u21d2': 'right double  arrow',
-  '\u21d3': 'down double arrow',
-  '\u21e6': 'left arrow',
-  '\u21e7': 'up arrow',
-  '\u21e8': 'right arrow',
-  '\u21e9': 'down arrow',
-  '\u2303': 'control',
-  '\u2318': 'command',
-  '\u2325': 'option',
-  '\u25b2': 'up triangle',
-  '\u25b3': 'up triangle',
-  '\u25b4': 'up triangle',
-  '\u25b5': 'up triangle',
-  '\u25b6': 'right triangle',
-  '\u25b7': 'right triangle',
-  '\u25b8': 'right triangle',
-  '\u25b9': 'right triangle',
-  '\u25ba': 'right pointer',
-  '\u25bb': 'right pointer',
-  '\u25bc': 'down triangle',
-  '\u25bd': 'down triangle',
-  '\u25be': 'down triangle',
-  '\u25bf': 'down triangle',
-  '\u25c0': 'left triangle',
-  '\u25c1': 'left triangle',
-  '\u25c2': 'left triangle',
-  '\u25c3': 'left triangle',
-  '\u25c4': 'left pointer',
-  '\u25c5': 'left pointer',
-  '\uf8ff': 'apple',
-  '£': 'pound sterling',
-};
-
-/**
  * Substitution dictionary regexp.
  * @private {RegExp}
  */
@@ -564,9 +307,6 @@
 AbstractTts.repetitionRegexp_ =
     /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>\u2022\u25e6\u25a0])\1{2,}/g;
 
-/** TTS phonetic-characters property. @type {string} */
-AbstractTts.PHONETIC_CHARACTERS = 'phoneticCharacters';
-
 /**
  * Regexp filter for negative dollar and pound amounts.
  * @private {RegExp}
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
index 98ca8c813..a4be0333 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
@@ -351,7 +351,7 @@
    * Method that updates the punctuation echo level, and also persists setting
    * to local storage.
    * @param {number} punctuationEcho The index of the desired punctuation echo
-   *     level in AbstractTts.PUNCTUATION_ECHOES.
+   *     level in PunctuationEchoes.
    * @return {!Promise}
    */
   async updatePunctuationEcho(punctuationEcho) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_types.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_types.js
index 19fc719..9787976 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_types.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_types.js
@@ -128,3 +128,223 @@
     Object.assign(this, opt_initialValues);
   }
 }
+
+/**
+ * A collection of TTS personalities to differentiate text.
+ * @type {!Object<!TtsSpeechProperties>}
+ */
+export const Personality = {
+  // TTS personality for annotations - text spoken by ChromeVox that
+  // elaborates on a user interface element but isn't displayed on-screen.
+  ANNOTATION: new TtsSpeechProperties({
+    'relativePitch': -0.25,
+    // TODO:(rshearer) Added this color change for I/O presentation.
+    'color': 'yellow',
+    'punctuationEcho': 'none',
+  }),
+
+  // TTS personality for announcements - text spoken by ChromeVox that
+  // isn't tied to any user interface elements.
+  ANNOUNCEMENT: new TtsSpeechProperties({
+    'punctuationEcho': 'none',
+  }),
+
+  // TTS personality for an aside - text in parentheses.
+  ASIDE: new TtsSpeechProperties({
+    'relativePitch': -0.1,
+    'color': '#669',
+  }),
+
+  // TTS personality for capital letters.
+  CAPITAL: new TtsSpeechProperties({
+    'relativePitch': 0.2,
+  }),
+
+  // TTS personality for deleted text.
+  DELETED: new TtsSpeechProperties({
+    'punctuationEcho': 'none',
+    'relativePitch': -0.6,
+  }),
+
+  // TTS personality for emphasis or italicized text.
+  EMPHASIS: new TtsSpeechProperties({
+    'relativeVolume': 0.1,
+    'relativeRate': -0.1,
+    'color': '#6bb',
+    'fontWeight': 'bold',
+  }),
+
+  // TTS personality for quoted text.
+  QUOTE: new TtsSpeechProperties({
+    'relativePitch': 0.1,
+    'color': '#b6b',
+    'fontWeight': 'bold',
+  }),
+
+  // TTS personality for strong or bold text.
+  STRONG: new TtsSpeechProperties({
+    'relativePitch': 0.1,
+    'color': '#b66',
+    'fontWeight': 'bold',
+  }),
+
+  // TTS personality for alerts from the system, such as battery level
+  // warnings.
+  SYSTEM_ALERT: new TtsSpeechProperties({
+    'punctuationEcho': 'none',
+    'doNotInterrupt': true,
+  }),
+};
+
+/**
+ * Various TTS-related settings keys.
+ * @enum {string}
+ */
+export const TtsSettings = {
+  // Color is for the lens display.
+  COLOR: 'color',
+  FONT_WEIGHT: 'fontWeight',
+  LANG: 'lang',
+  PAUSE: 'pause',
+  PHONETIC_CHARACTERS: 'phoneticCharacters',
+  PITCH: 'pitch',
+  PUNCTUATION_ECHO: 'punctuationEcho',
+  RATE: 'rate',
+  RELATIVE_PITCH: 'relativePitch',
+  RELATIVE_RATE: 'relativeRate',
+  RELATIVE_VOLUME: 'relativeVolume',
+  VOLUME: 'volume',
+};
+
+/**
+ * List of punctuation echoes that the user can cycle through.
+ * @type {!Array<{name:(string),
+ * msg:(string),
+ * regexp:(RegExp),
+ * clear:(boolean)}>}
+ */
+export const PunctuationEchoes = [
+  // Punctuation echoed for the 'none' option.
+  {
+    name: 'none',
+    msg: 'no_punctuation',
+    regexp: /[-$#"()*;:<>\n\\\/+='~`@_]/g,
+    clear: true,
+  },
+
+  // Punctuation echoed for the 'some' option.
+  {
+    name: 'some',
+    msg: 'some_punctuation',
+    regexp: /[$#"*<>\\\/\{\}+=~`%\u2022\u25e6\u25a0]/g,
+    clear: false,
+  },
+
+  // Punctuation echoed for the 'all' option.
+  {
+    name: 'all',
+    msg: 'all_punctuation',
+    regexp: /[-$#"()*;:<>\n\\\/\{\}\[\]+='~`!@_.,?%\u2022\u25e6\u25a0]/g,
+    clear: false,
+  },
+];
+
+/**
+ * Character dictionary. These symbols are replaced with their human readable
+ * equivalents. This replacement only occurs for single character utterances.
+ * @type {Object<string>}
+ */
+export const CharacterDictionary = {
+  ' ': 'space',
+  '\u00a0': 'space',
+  '`': 'backtick',
+  '~': 'tilde',
+  '!': 'exclamation',
+  '@': 'at',
+  '#': 'pound',
+  '$': 'dollar',
+  '%': 'percent',
+  '^': 'caret',
+  '&': 'ampersand',
+  '*': 'asterisk',
+  '(': 'open_paren',
+  ')': 'close_paren',
+  '-': 'dash',
+  '_': 'underscore',
+  '=': 'equals',
+  '+': 'plus',
+  '[': 'left_bracket',
+  ']': 'right_bracket',
+  '{': 'left_brace',
+  '}': 'right_brace',
+  '|': 'pipe',
+  ';': 'semicolon',
+  ':': 'colon',
+  ',': 'comma',
+  '.': 'dot',
+  '<': 'less_than',
+  '>': 'greater_than',
+  '/': 'slash',
+  '?': 'question_mark',
+  '"': 'quote',
+  '\'': 'apostrophe',
+  '\t': 'tab',
+  '\r': 'return',
+  '\n': 'new_line',
+  '\\': 'backslash',
+  '\u2022': 'bullet',
+  '\u25e6': 'white_bullet',
+  '\u25a0': 'square_bullet',
+};
+
+/**
+ * Substitution dictionary. These symbols or patterns are ALWAYS substituted
+ * whenever they occur, so this should be reserved only for unicode characters
+ * and characters that never have any different meaning in context.
+ *
+ * For example, do not include '$' here because $2 should be read as
+ * "two dollars".
+ * @type {Object<string>}
+ */
+export const SubstitutionDictionary = {
+  '://': 'colon slash slash',
+  '\u00bc': 'one fourth',
+  '\u00bd': 'one half',
+  '\u2190': 'left arrow',
+  '\u2191': 'up arrow',
+  '\u2192': 'right arrow',
+  '\u2193': 'down arrow',
+  '\u21d0': 'left double arrow',
+  '\u21d1': 'up double arrow',
+  '\u21d2': 'right double  arrow',
+  '\u21d3': 'down double arrow',
+  '\u21e6': 'left arrow',
+  '\u21e7': 'up arrow',
+  '\u21e8': 'right arrow',
+  '\u21e9': 'down arrow',
+  '\u2303': 'control',
+  '\u2318': 'command',
+  '\u2325': 'option',
+  '\u25b2': 'up triangle',
+  '\u25b3': 'up triangle',
+  '\u25b4': 'up triangle',
+  '\u25b5': 'up triangle',
+  '\u25b6': 'right triangle',
+  '\u25b7': 'right triangle',
+  '\u25b8': 'right triangle',
+  '\u25b9': 'right triangle',
+  '\u25ba': 'right pointer',
+  '\u25bb': 'right pointer',
+  '\u25bc': 'down triangle',
+  '\u25bd': 'down triangle',
+  '\u25be': 'down triangle',
+  '\u25bf': 'down triangle',
+  '\u25c0': 'left triangle',
+  '\u25c1': 'left triangle',
+  '\u25c2': 'left triangle',
+  '\u25c3': 'left triangle',
+  '\u25c4': 'left pointer',
+  '\u25c5': 'left pointer',
+  '\uf8ff': 'apple',
+  '£': 'pound sterling',
+};
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js
index 188f0a3..9126157 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js
@@ -6,12 +6,12 @@
  * @fileoverview ChromeVox options page.
  */
 import {constants} from '../../common/constants.js';
-import {AbstractTts} from '../common/abstract_tts.js';
 import {BackgroundBridge} from '../common/background_bridge.js';
 import {BrailleTable} from '../common/braille/braille_table.js';
 import {ExtensionBridge} from '../common/extension_bridge.js';
 import {Msgs} from '../common/msgs.js';
 import {PanelCommand, PanelCommandType} from '../common/panel_command.js';
+import {PunctuationEchoes, TtsSettings} from '../common/tts_types.js';
 
 import {BluetoothBrailleDisplayUI} from './bluetooth_braille_display_ui.js';
 
@@ -96,10 +96,9 @@
       }
     }
 
-    if (localStorage[AbstractTts.PUNCTUATION_ECHO]) {
+    if (localStorage[TtsSettings.PUNCTUATION_ECHO]) {
       const currentPunctuationEcho =
-          AbstractTts
-              .PUNCTUATION_ECHOES[localStorage[AbstractTts.PUNCTUATION_ECHO]];
+          PunctuationEchoes[localStorage[TtsSettings.PUNCTUATION_ECHO]];
       for (let i = 0, opt; opt = $('punctuationEcho').options[i]; ++i) {
         if (opt.id === currentPunctuationEcho.name) {
           opt.setAttribute('selected', '');
@@ -468,7 +467,7 @@
         OptionsPage.setEventStreamFilter(target.name, target.checked);
       } else if (target.id === 'punctuationEcho') {
         const selectedPunctuationEcho = target.options[target.selectedIndex].id;
-        const punctuationEcho = AbstractTts.PUNCTUATION_ECHOES.findIndex(
+        const punctuationEcho = PunctuationEchoes.findIndex(
             echo => echo.name === selectedPunctuationEcho);
         BackgroundBridge.TtsBackground.updatePunctuationEcho(punctuationEcho);
       } else if (target.classList.contains('pref')) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
index 3a9235a..f7ba5d9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
@@ -24,7 +24,7 @@
     await importModule(
         'CommandHandlerInterface',
         '/chromevox/background/command_handler_interface.js');
-    await importModule('AbstractTts', '/chromevox/common/abstract_tts.js');
+    await importModule('TtsSettings', '/chromevox/common/tts_types.js');
     await importModule('EventGenerator', '/common/event_generator.js');
     await importModule('KeyCode', '/common/key_code.js');
   }
@@ -108,7 +108,7 @@
           .call(() => {
             assertEquals(
                 PUNCTUATION_ECHO_NONE,
-                localStorage[AbstractTts.PUNCTUATION_ECHO]);
+                localStorage[TtsSettings.PUNCTUATION_ECHO]);
           })
 
           .call(press(KeyCode.DOWN))
@@ -118,7 +118,7 @@
           .call(() => {
             assertEquals(
                 PUNCTUATION_ECHO_SOME,
-                localStorage[AbstractTts.PUNCTUATION_ECHO]);
+                localStorage[TtsSettings.PUNCTUATION_ECHO]);
           })
 
           .call(press(KeyCode.DOWN))
@@ -126,7 +126,7 @@
           .call(() => {
             assertEquals(
                 PUNCTUATION_ECHO_ALL,
-                localStorage[AbstractTts.PUNCTUATION_ECHO]);
+                localStorage[TtsSettings.PUNCTUATION_ECHO]);
           });
 
       await mockFeedback.replay();
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index cda0a5fb..b3f7991 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -221,11 +221,9 @@
     "screens/common/guest_tos.m.js",
     "screens/common/hw_data_collection.m.js",
     "screens/common/recommend_apps.m.js",
-    "screens/common/signin_fatal_error.m.js",
     "screens/common/smart_privacy_protection.m.js",
     "screens/common/sync_consent.m.js",
     "screens/oobe/enterprise_enrollment.m.js",
-    "screens/oobe/packaged_license.m.js",
     "screens/oobe/quick_start.m.js",
 
     # Special files.
@@ -287,6 +285,7 @@
     "screens/common/parental_handoff.js",
     "screens/common/pin_setup.js",
     "screens/common/saml_confirm_password.js",
+    "screens/common/signin_fatal_error.js",
     "screens/common/theme_selection.js",
     "screens/common/tpm_error.js",
     "screens/common/user_creation.js",
@@ -307,6 +306,7 @@
     "screens/oobe/enable_debugging.js",
     "screens/oobe/hid_detection.js",
     "screens/oobe/oobe_network.js",
+    "screens/oobe/packaged_license.js",
     "screens/oobe/update.js",
     "screens/oobe/welcome.js",
     "screens/oobe/welcome_dialog.js",
diff --git a/chrome/browser/resources/chromeos/login/screens.js b/chrome/browser/resources/chromeos/login/screens.js
index 3217e13..ee765ce1 100644
--- a/chrome/browser/resources/chromeos/login/screens.js
+++ b/chrome/browser/resources/chromeos/login/screens.js
@@ -35,7 +35,7 @@
 import './screens/common/pin_setup.js';
 import './screens/common/recommend_apps.m.js';
 import './screens/common/saml_confirm_password.js';
-import './screens/common/signin_fatal_error.m.js';
+import './screens/common/signin_fatal_error.js';
 import './screens/common/smart_privacy_protection.m.js';
 import './screens/common/sync_consent.m.js';
 import './screens/common/theme_selection.js';
@@ -60,7 +60,7 @@
 import './screens/oobe/enterprise_enrollment.m.js';
 import './screens/oobe/hid_detection.js';
 import './screens/oobe/oobe_network.js';
-import './screens/oobe/packaged_license.m.js';
+import './screens/oobe/packaged_license.js';
 import './screens/oobe/quick_start.m.js';
 import './screens/oobe/update.js';
 import './screens/oobe/welcome.js';
@@ -146,15 +146,15 @@
  * List of screens that are used during the `oobe` flow only.
  */
 export const oobeScreensList = [
-    {tag: 'auto-enrollment-check-element', id: 'auto-enrollment-check'},
-    {tag: 'demo-preferences-element', id: 'demo-preferences'},
-    {tag: 'demo-setup-element', id: 'demo-setup'},
-    {tag: 'enable-debugging-element', id: 'debugging'},
-    {tag: 'enterprise-enrollment-element', id: 'enterprise-enrollment'},
-    {tag: 'hid-detection-element', id: 'hid-detection'},
-    {tag: 'oobe-network-element', id: 'network-selection'},
-    {tag: 'packaged-license-element', id: 'packaged-license'},
-    {tag: 'quick-start-element', id: 'quick-start'},
-    {tag: 'update-element', id: 'oobe-update'},
-    {tag: 'oobe-welcome-element', id: 'connect'},
-];
+  {tag: 'auto-enrollment-check-element', id: 'auto-enrollment-check'},
+  {tag: 'demo-preferences-element', id: 'demo-preferences'},
+  {tag: 'demo-setup-element', id: 'demo-setup'},
+  {tag: 'enable-debugging-element', id: 'debugging'},
+  {tag: 'enterprise-enrollment-element', id: 'enterprise-enrollment'},
+  {tag: 'hid-detection-element', id: 'hid-detection'},
+  {tag: 'oobe-network-element', id: 'network-selection'},
+  {tag: 'packaged-license-element', id: 'packaged-license'},
+  {tag: 'quick-start-element', id: 'quick-start'},
+  {tag: 'update-element', id: 'oobe-update'},
+  {tag: 'oobe-welcome-element', id: 'connect'},
+];
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
index c1aef40..994bd2246 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
@@ -18,7 +18,6 @@
     ":guest_tos_module",
     ":hw_data_collection_module",
     ":recommend_apps_module",
-    ":signin_fatal_error_module",
     ":smart_privacy_protection_module",
     ":sync_consent_module",
   ]
@@ -70,7 +69,7 @@
     ":pin_setup",
     ":recommend_apps.m",
     ":saml_confirm_password",
-    ":signin_fatal_error.m",
+    ":signin_fatal_error",
     ":smart_privacy_protection.m",
     ":sync_consent.m",
     ":theme_selection",
@@ -551,8 +550,8 @@
   extra_deps = [ ":web_components" ]
 }
 
-js_library("signin_fatal_error.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.m.js" ]
+js_library("signin_fatal_error") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.js" ]
   deps = [
     "../../components:display_manager_types.m",
     "../../components:oobe_types.m",
@@ -563,7 +562,7 @@
     "../../components/dialogs:oobe_adaptive_dialog",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":signin_fatal_error_module" ]
+  extra_deps = [ ":web_components" ]
 }
 
 js_library("smart_privacy_protection.m") {
@@ -711,15 +710,6 @@
   migrated_imports = oobe_migrated_imports
 }
 
-polymer_modulizer("signin_fatal_error") {
-  js_file = "signin_fatal_error.js"
-  html_file = "signin_fatal_error.html"
-  html_type = "dom-module"
-  auto_imports = oobe_auto_imports
-  migrated_imports = oobe_migrated_imports
-  namespace_rewrites = oobe_namespace_rewrites
-}
-
 polymer_modulizer("smart_privacy_protection") {
   js_file = "smart_privacy_protection.js"
   html_file = "smart_privacy_protection.html"
@@ -764,6 +754,7 @@
     "parental_handoff.js",
     "pin_setup.js",
     "saml_confirm_password.js",
+    "signin_fatal_error.js",
     "theme_selection.js",
     "tpm_error.js",
     "user_creation.js",
diff --git a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.html b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.html
index 7a15bfcd..20e4f51 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.html
+++ b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.html
@@ -4,46 +4,25 @@
 found in the LICENSE file.
 -->
 
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/action_link.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-
-<link rel="import" href="../../components/display_manager_types.html">
-<link rel="import" href="../../components/oobe_icons.html">
-<link rel="import" href="../../components/oobe_types.html">
-<link rel="import" href="../../components/behaviors/login_screen_behavior.html">
-<link rel="import" href="../../components/behaviors/oobe_dialog_host_behavior.html">
-<link rel="import" href="../../components/behaviors/oobe_i18n_behavior.html">
-<link rel="import" href="../../components/buttons/oobe_text_button.html">
-<link rel="import" href="../../components/common_styles/common_styles.html">
-<link rel="import" href="../../components/common_styles/oobe_dialog_host_styles.html">
-<link rel="import" href="../../components/dialogs/oobe_adaptive_dialog.html">
-
-<dom-module id="signin-fatal-error-element">
-  <template>
-    <style include="oobe-dialog-host-styles">
-    </style>
-    <oobe-adaptive-dialog id="signinFatalErrorDialog" role="dialog">
-      <iron-icon slot="icon" icon="oobe-32:alert"></iron-icon>
-      <h1 slot="title">
-        [[i18nDynamic(locale, 'errorGenericFatalErrorTitle')]]
-      </h1>
-      <p id="subtitle" slot="subtitle">
-        [[errorSubtitle_]]
-      </p>
-      <div slot="subtitle" hidden="[[!keyboardHint_]]">[[keyboardHint_]]</div>
-      <div slot="subtitle" hidden="[[!details_]]">[[details_]]</div>
-      <a slot="subtitle" on-click="onHelpLinkClicked_"
-          hidden="[[!helpLinkText_]]" class="oobe-local-link" is="action-link">
-        [[helpLinkText_]]
-      </a>
-      <div slot="bottom-buttons">
-        <oobe-text-button id="actionButton" inverse class="focus-on-show"
-            text-key="[[computeButtonKey_(errorState_)]]" on-tap="onClick_">
-        </oobe-text-button>
-      </div>
-    </oobe-adaptive-dialog>
-  </template>
-  <script src="signin_fatal_error.js"></script>
-</dom-module>
+<style include="oobe-dialog-host-styles">
+</style>
+<oobe-adaptive-dialog id="signinFatalErrorDialog" role="dialog">
+  <iron-icon slot="icon" icon="oobe-32:alert"></iron-icon>
+  <h1 slot="title">
+    [[i18nDynamic(locale, 'errorGenericFatalErrorTitle')]]
+  </h1>
+  <p id="subtitle" slot="subtitle">
+    [[errorSubtitle_]]
+  </p>
+  <div slot="subtitle" hidden="[[!keyboardHint_]]">[[keyboardHint_]]</div>
+  <div slot="subtitle" hidden="[[!details_]]">[[details_]]</div>
+  <a slot="subtitle" on-click="onHelpLinkClicked_"
+      hidden="[[!helpLinkText_]]" class="oobe-local-link" is="action-link">
+    [[helpLinkText_]]
+  </a>
+  <div slot="bottom-buttons">
+    <oobe-text-button id="actionButton" inverse class="focus-on-show"
+        text-key="[[computeButtonKey_(errorState_)]]" on-tap="onClick_">
+    </oobe-text-button>
+  </div>
+</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.js b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.js
index 8b650fb..c8923f8 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.js
+++ b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.js
@@ -6,7 +6,22 @@
  * @fileoverview Polymer element for signin fatal error.
  */
 
-/* #js_imports_placeholder */
+import '//resources/js/action_link.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../../components/oobe_icons.m.js';
+import '../../components/common_styles/common_styles.m.js';
+import '../../components/common_styles/oobe_dialog_host_styles.m.js';
+import '../../components/dialogs/oobe_adaptive_dialog.js';
+
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LoginScreenBehavior, LoginScreenBehaviorInterface} from '../../components/behaviors/login_screen_behavior.m.js';
+import {OobeDialogHostBehavior} from '../../components/behaviors/oobe_dialog_host_behavior.m.js';
+import {OobeI18nBehavior, OobeI18nBehaviorInterface} from '../../components/behaviors/oobe_i18n_behavior.m.js';
+import {OobeTextButton} from '../../components/buttons/oobe_text_button.js';
+import {OOBE_UI_STATE, SCREEN_GAIA_SIGNIN} from '../../components/display_manager_types.m.js';
+import {OobeTypes} from '../../components/oobe_types.m.js';
+
 
 /**
  * @constructor
@@ -14,9 +29,9 @@
  * @implements {LoginScreenBehaviorInterface}
  * @implements {OobeI18nBehaviorInterface}
  */
-const SigninFatalErrorBase = Polymer.mixinBehaviors(
+const SigninFatalErrorBase = mixinBehaviors(
     [OobeI18nBehavior, OobeDialogHostBehavior, LoginScreenBehavior],
-    Polymer.Element);
+    PolymerElement);
 
 /**
  * @typedef {{
@@ -33,7 +48,10 @@
     return 'signin-fatal-error-element';
   }
 
-  /* #html_template_placeholder */
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
 
   static get properties() {
     return {
@@ -52,6 +70,7 @@
        */
       errorState_: {
         type: Number,
+        value: 0,
       },
 
       /**
@@ -60,6 +79,7 @@
        */
       params_: {
         type: Object,
+        value: {},
       },
 
       keyboardHint_: {
@@ -76,11 +96,6 @@
     };
   }
 
-  constructor() {
-    super();
-    this.errorState_ = 0;
-    this.params_ = {};
-  }
 
   ready() {
     super.ready();
diff --git a/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn
index 2d8fca0..a244129 100644
--- a/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn
@@ -11,7 +11,6 @@
 group("polymer3_elements") {
   public_deps = [
     ":enterprise_enrollment_module",
-    ":packaged_license_module",
     ":quick_start_module",
   ]
 }
@@ -33,7 +32,7 @@
     ":enterprise_enrollment.m",
     ":hid_detection",
     ":oobe_network",
-    ":packaged_license.m",
+    ":packaged_license",
     ":quick_start.m",
     ":update",
     ":welcome",
@@ -158,8 +157,8 @@
   extra_deps = [ ":web_components" ]
 }
 
-js_library("packaged_license.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.m.js" ]
+js_library("packaged_license") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.js" ]
   deps = [
     "../../components/behaviors:login_screen_behavior.m",
     "../../components/behaviors:oobe_dialog_host_behavior.m",
@@ -167,7 +166,7 @@
     "../../components/dialogs:oobe_adaptive_dialog",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":packaged_license_module" ]
+  extra_deps = [ ":web_components" ]
 }
 
 js_library("update") {
@@ -241,15 +240,6 @@
   namespace_rewrites = oobe_namespace_rewrites
 }
 
-polymer_modulizer("packaged_license") {
-  js_file = "packaged_license.js"
-  html_file = "packaged_license.html"
-  html_type = "dom-module"
-  auto_imports = oobe_auto_imports
-  migrated_imports = oobe_migrated_imports
-  namespace_rewrites = oobe_namespace_rewrites
-}
-
 html_to_js("web_components") {
   js_files = [
     "auto_enrollment_check.js",
@@ -258,6 +248,7 @@
     "enable_debugging.js",
     "hid_detection.js",
     "oobe_network.js",
+    "packaged_license.js",
     "update.js",
     "welcome.js",
     "welcome_dialog.js",
diff --git a/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.html b/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.html
index 3ae7692..d0d8e29 100644
--- a/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.html
+++ b/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.html
@@ -4,50 +4,30 @@
 found in the LICENSE file.
 -->
 
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_shared_vars.css.html">
-<link rel="import" href="chrome://resources/ash/common/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-
-<link rel="import" href="../../components/oobe_icons.html">
-<link rel="import" href="../../components/behaviors/login_screen_behavior.html">
-<link rel="import" href="../../components/behaviors/oobe_dialog_host_behavior.html">
-<link rel="import" href="../../components/behaviors/oobe_i18n_behavior.html">
-<link rel="import" href="../../components/buttons/oobe_text_button.html">
-<link rel="import" href="../../components/common_styles/common_styles.html">
-<link rel="import" href="../../components/common_styles/oobe_dialog_host_styles.html">
-<link rel="import" href="../../components/dialogs/oobe_adaptive_dialog.html">
-
-<dom-module id="packaged-license-element">
-  <template>
-    <style include="oobe-dialog-host-styles"></style>
-    <oobe-adaptive-dialog id="packagedLicenseDialog"
-        aria-label$="[[i18nDynamic(locale, 'oobePackagedLicenseTitle')]]"
-        role="dialog">
-      <iron-icon slot="icon" icon="oobe-32:enterprise"></iron-icon>
-      <h1 slot="title">
-        [[i18nDynamic(locale, 'oobePackagedLicenseTitle')]]
-      </h1>
-      <p slot="subtitle">
-        [[i18nDynamic(locale, 'oobePackagedLicenseSubtitleP1')]]
-      </p>
-      <p slot="subtitle">
-        [[i18nDynamic(locale, 'oobePackagedLicenseSubtitleP2')]]
-      </p>
-      <div slot="content" class="flex layout vertical center center-justified">
-        <img src="../../images/enrollment_complete.svg"
-            class="oobe-illustration" aria-hidden="true">
-      </div>
-      <div slot="bottom-buttons">
-        <oobe-text-button id="dont-enroll-button"
-            text-key="oobePackagedLicenseDontEnroll"
-            on-click="onDontEnrollButtonPressed_"></oobe-text-button>
-        <oobe-text-button id="enroll-button"
-            text-key="oobePackagedLicenseEnroll" class="focus-on-show"
-            inverse on-click="onEnrollButtonPressed_"></oobe-text-button>
-      </div>
-    </oobe-adaptive-dialog>
-  </template>
-  <script src="packaged_license.js"></script>
-</dom-module>
+<style include="oobe-dialog-host-styles"></style>
+<oobe-adaptive-dialog id="packagedLicenseDialog"
+    aria-label$="[[i18nDynamic(locale, 'oobePackagedLicenseTitle')]]"
+    role="dialog">
+  <iron-icon slot="icon" icon="oobe-32:enterprise"></iron-icon>
+  <h1 slot="title">
+    [[i18nDynamic(locale, 'oobePackagedLicenseTitle')]]
+  </h1>
+  <p slot="subtitle">
+    [[i18nDynamic(locale, 'oobePackagedLicenseSubtitleP1')]]
+  </p>
+  <p slot="subtitle">
+    [[i18nDynamic(locale, 'oobePackagedLicenseSubtitleP2')]]
+  </p>
+  <div slot="content" class="flex layout vertical center center-justified">
+    <img src="../../images/enrollment_complete.svg"
+        class="oobe-illustration" aria-hidden="true">
+  </div>
+  <div slot="bottom-buttons">
+    <oobe-text-button id="dont-enroll-button"
+        text-key="oobePackagedLicenseDontEnroll"
+        on-click="onDontEnrollButtonPressed_"></oobe-text-button>
+    <oobe-text-button id="enroll-button"
+        text-key="oobePackagedLicenseEnroll" class="focus-on-show"
+        inverse on-click="onEnrollButtonPressed_"></oobe-text-button>
+  </div>
+</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.js b/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.js
index 45c73f4..20113e4e 100644
--- a/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.js
+++ b/chrome/browser/resources/chromeos/login/screens/oobe/packaged_license.js
@@ -6,7 +6,20 @@
  * @fileoverview Polymer element for Packaged License screen.
  */
 
-/* #js_imports_placeholder */
+import '//resources/cr_elements/cr_shared_vars.css.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../../components/oobe_icons.m.js';
+import '../../components/common_styles/common_styles.m.js';
+import '../../components/common_styles/oobe_dialog_host_styles.m.js';
+
+import {afterNextRender, html, mixinBehaviors, Polymer, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LoginScreenBehavior, LoginScreenBehaviorInterface} from '../../components/behaviors/login_screen_behavior.m.js';
+import {OobeDialogHostBehavior} from '../../components/behaviors/oobe_dialog_host_behavior.m.js';
+import {OobeI18nBehavior, OobeI18nBehaviorInterface} from '../../components/behaviors/oobe_i18n_behavior.m.js';
+import {OobeTextButton} from '../../components/buttons/oobe_text_button.js';
+import {OobeAdaptiveDialog} from '../../components/dialogs/oobe_adaptive_dialog.js';
+
 
 /**
  * @constructor
@@ -14,9 +27,9 @@
  * @implements {LoginScreenBehaviorInterface}
  * @implements {OobeI18nBehaviorInterface}
  */
-const PackagedLicenseScreenBase = Polymer.mixinBehaviors(
+const PackagedLicenseScreenBase = mixinBehaviors(
     [OobeI18nBehavior, OobeDialogHostBehavior, LoginScreenBehavior],
-    Polymer.Element);
+    PolymerElement);
 
 /**
  * @typedef {{
@@ -25,21 +38,22 @@
  */
 PackagedLicenseScreenBase.$;
 
+/**
+ * @polymer
+ */
 class PackagedLicenseScreen extends PackagedLicenseScreenBase {
   static get is() {
     return 'packaged-license-element';
   }
 
-  /* #html_template_placeholder */
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
   static get properties() {
     return {};
   }
 
-  constructor() {
-    super();
-  }
-
   /** @override */
   ready() {
     super.ready();
diff --git a/chrome/browser/resources/connectors_internals/BUILD.gn b/chrome/browser/resources/connectors_internals/BUILD.gn
index cf9590e..db4820d 100644
--- a/chrome/browser/resources/connectors_internals/BUILD.gn
+++ b/chrome/browser/resources/connectors_internals/BUILD.gn
@@ -14,9 +14,7 @@
     "device_trust_connector.ts",
   ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/connectors_internals/connectors_internals.mojom-webui.js" ]
-  mojo_files_deps = [
-    "//chrome/browser/ui/webui/connectors_internals:mojo_bindings_webui_js",
-  ]
+  mojo_files_deps = [ "//chrome/browser/ui/webui/connectors_internals:mojo_bindings_js__generator" ]
 
   html_to_wrapper_template = "native"
 
diff --git a/chrome/browser/resources/discards/BUILD.gn b/chrome/browser/resources/discards/BUILD.gn
index c1c57311..2ce4fba1 100644
--- a/chrome/browser/resources/discards/BUILD.gn
+++ b/chrome/browser/resources/discards/BUILD.gn
@@ -80,8 +80,8 @@
 
 copy("copy_mojo") {
   deps = [
-    "//chrome/browser/resource_coordinator:mojo_bindings_webui_js",
-    "//chrome/browser/ui/webui/discards:mojo_bindings_webui_js",
+    "//chrome/browser/resource_coordinator:mojo_bindings_js__generator",
+    "//chrome/browser/ui/webui/discards:mojo_bindings_js__generator",
   ]
   sources = [
     "$root_gen_dir/mojom-webui/chrome/browser/resource_coordinator/lifecycle_unit_state.mojom-webui.js",
diff --git a/chrome/browser/resources/downloads/BUILD.gn b/chrome/browser/resources/downloads/BUILD.gn
index 6b102545..a806dad5 100644
--- a/chrome/browser/resources/downloads/BUILD.gn
+++ b/chrome/browser/resources/downloads/BUILD.gn
@@ -34,7 +34,7 @@
   icons_html_files = [ "icons.html" ]
 
   mojo_files_deps =
-      [ "//chrome/browser/ui/webui/downloads:mojo_bindings_webui_js" ]
+      [ "//chrome/browser/ui/webui/downloads:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/downloads/downloads.mojom-webui.js" ]
 
   ts_composite = true
diff --git a/chrome/browser/resources/engagement/BUILD.gn b/chrome/browser/resources/engagement/BUILD.gn
index d235373..520aabc 100644
--- a/chrome/browser/resources/engagement/BUILD.gn
+++ b/chrome/browser/resources/engagement/BUILD.gn
@@ -5,7 +5,8 @@
 import("//tools/typescript/ts_library.gni")
 
 copy("copy_src_and_mojo") {
-  deps = [ "//components/site_engagement/core/mojom:mojo_bindings_webui_js" ]
+  deps =
+      [ "//components/site_engagement/core/mojom:mojo_bindings_js__generator" ]
   sources = [
     "$root_gen_dir/mojom-webui/components/site_engagement/core/mojom/site_engagement_details.mojom-webui.js",
     "site_engagement.ts",
diff --git a/chrome/browser/resources/feed_internals/BUILD.gn b/chrome/browser/resources/feed_internals/BUILD.gn
index af0c468..23540308d 100644
--- a/chrome/browser/resources/feed_internals/BUILD.gn
+++ b/chrome/browser/resources/feed_internals/BUILD.gn
@@ -17,7 +17,7 @@
   non_web_component_files = [ "feed_internals.ts" ]
 
   mojo_files_deps =
-      [ "//chrome/browser/ui/webui/feed_internals:mojo_bindings_webui_js" ]
+      [ "//chrome/browser/ui/webui/feed_internals:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/feed_internals/feed_internals.mojom-webui.js" ]
 
   ts_deps = [
diff --git a/chrome/browser/resources/image_editor/BUILD.gn b/chrome/browser/resources/image_editor/BUILD.gn
index 208a4c8..f0d4664 100644
--- a/chrome/browser/resources/image_editor/BUILD.gn
+++ b/chrome/browser/resources/image_editor/BUILD.gn
@@ -39,7 +39,8 @@
 }
 
 copy("copy_mojo") {
-  deps = [ "//chrome/browser/ui/webui/image_editor:mojo_bindings_webui_js" ]
+  deps =
+      [ "//chrome/browser/ui/webui/image_editor:mojo_bindings_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/image_editor/image_editor.mojom-webui.js" ]
   outputs = [ "$preprocess_folder/{{source_file_part}}" ]
 }
diff --git a/chrome/browser/resources/internals/user_education/BUILD.gn b/chrome/browser/resources/internals/user_education/BUILD.gn
index 01b3588..315c144 100644
--- a/chrome/browser/resources/internals/user_education/BUILD.gn
+++ b/chrome/browser/resources/internals/user_education/BUILD.gn
@@ -28,9 +28,7 @@
 }
 
 copy("copy_mojo") {
-  deps = [
-    "//chrome/browser/ui/webui/internals/user_education:mojo_bindings_webui_js",
-  ]
+  deps = [ "//chrome/browser/ui/webui/internals/user_education:mojo_bindings_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/internals/user_education/user_education_internals.mojom-webui.js" ]
   outputs = [ "$preprocess_folder/{{source_file_part}}" ]
 }
diff --git a/chrome/browser/resources/omnibox/BUILD.gn b/chrome/browser/resources/omnibox/BUILD.gn
index 285f7a6..2d545f21 100644
--- a/chrome/browser/resources/omnibox/BUILD.gn
+++ b/chrome/browser/resources/omnibox/BUILD.gn
@@ -27,7 +27,7 @@
   ]
 
   mojo_files_deps =
-      [ "//chrome/browser/ui/webui/omnibox:mojo_bindings_webui_js" ]
+      [ "//chrome/browser/ui/webui/omnibox:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/omnibox/omnibox.mojom-webui.js" ]
 
   ts_deps = [
diff --git a/chrome/browser/resources/reset_password/BUILD.gn b/chrome/browser/resources/reset_password/BUILD.gn
index c892db37..9e274c13 100644
--- a/chrome/browser/resources/reset_password/BUILD.gn
+++ b/chrome/browser/resources/reset_password/BUILD.gn
@@ -10,7 +10,8 @@
 # Copy mojo and src files to the same location so that they can be passed to
 # ts_library.
 copy("copy_src_and_mojo") {
-  deps = [ "//chrome/browser/ui/webui/reset_password:mojo_bindings_webui_js" ]
+  deps =
+      [ "//chrome/browser/ui/webui/reset_password:mojo_bindings_js__generator" ]
   sources = [
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/reset_password/reset_password.mojom-webui.js",
     "reset_password.ts",
diff --git a/chrome/browser/resources/segmentation_internals/BUILD.gn b/chrome/browser/resources/segmentation_internals/BUILD.gn
index 2a382e0a..c4a86146 100644
--- a/chrome/browser/resources/segmentation_internals/BUILD.gn
+++ b/chrome/browser/resources/segmentation_internals/BUILD.gn
@@ -11,9 +11,7 @@
     "segmentation_internals.ts",
     "segmentation_internals_browser_proxy.ts",
   ]
-  mojo_files_deps = [
-    "//chrome/browser/ui/webui/segmentation_internals:mojo_bindings_webui_js",
-  ]
+  mojo_files_deps = [ "//chrome/browser/ui/webui/segmentation_internals:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/segmentation_internals/segmentation_internals.mojom-webui.js" ]
   ts_deps = [ "//ui/webui/resources:library" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index f9898f6..b9aea33 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -332,7 +332,6 @@
     ":global_scroll_target_behavior",
     ":icon",
     ":lazy_load",
-    ":main_page_behavior",
     ":metrics_recorder",
     ":os_page_visibility",
     ":os_route",
@@ -377,15 +376,6 @@
   ]
 }
 
-js_library("main_page_behavior") {
-  deps = [
-    "..:router",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert",
-    "//ui/webui/resources/js:util",
-  ]
-}
-
 js_library("metrics_recorder") {
   deps = [
     "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_webui_js",
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
index efaaa6f7..088996f 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
@@ -63,11 +63,6 @@
 
   static get properties() {
     return {
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       focusConfig_: {
         type: Object,
         value() {
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.ts
index d26ec57..ac13eaa2 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.ts
@@ -61,12 +61,6 @@
 
   static get properties() {
     return {
-      /** Preferences state. */
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       showAddPortDialog_: {
         type: Boolean,
         value: false,
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
index 4a421f4..0c2912e 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
@@ -68,12 +68,6 @@
 
   static get properties() {
     return {
-      /** Preferences state. */
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       /**
        * Whether export / import UI should be displayed.
        */
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
index 858d499..d1291b7 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
@@ -46,11 +46,6 @@
 
   static get properties() {
     return {
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       hasMouse: Boolean,
 
       hasPointingStick: Boolean,
diff --git a/chrome/browser/resources/settings/chromeos/main_page_behavior.js b/chrome/browser/resources/settings/chromeos/main_page_behavior.js
deleted file mode 100644
index 2a3bfe3..0000000
--- a/chrome/browser/resources/settings/chromeos/main_page_behavior.js
+++ /dev/null
@@ -1,379 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/js/assert.js';
-import {beforeNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {MinimumRoutes, Route, Router} from '../router.js';
-
-import {ensureLazyLoaded} from './ensure_lazy_loaded.js';
-
-/**
- * @enum {string}
- * A categorization of every possible Settings URL, necessary for implementing
- * a finite state machine.
- */
-export const RouteState = {
-  // Initial state before anything has loaded yet.
-  INITIAL: 'initial',
-  // A dialog that has a dedicated URL (e.g. /importData).
-  DIALOG: 'dialog',
-  // A section (basically a scroll position within the top level page, e.g,
-  // /appearance.
-  SECTION: 'section',
-  // A subpage, or sub-subpage e.g, /searchEngins.
-  SUBPAGE: 'subpage',
-  // The top level Settings page, '/'.
-  TOP_LEVEL: 'top-level',
-};
-
-/**
- * @param {?Route=} route
- * @return {!RouteState}
- */
-function classifyRoute(route) {
-  if (!route) {
-    return RouteState.INITIAL;
-  }
-  const routes =
-      /** @type {!MinimumRoutes} */ (Router.getInstance().getRoutes());
-  if (route === routes.BASIC || route === routes.ABOUT) {
-    return RouteState.TOP_LEVEL;
-  }
-  if (route.isSubpage()) {
-    return RouteState.SUBPAGE;
-  }
-  if (route.isNavigableDialog) {
-    return RouteState.DIALOG;
-  }
-  return RouteState.SECTION;
-}
-
-/**
- * Responds to route changes by expanding, collapsing, or scrolling to
- * sections on the page. Expanded sections take up the full height of the
- * container. At most one section should be expanded at any given time.
- * @polymerBehavior
- */
-export const MainPageBehavior = {
-  /** @type {?HTMLElement} */
-  scroller: null,
-
-  /**
-   * A map holding all valid state transitions.
-   * @private {!Map<!RouteState, !RouteState>}
-   */
-  validTransitions_: (function() {
-    const allStates = new Set([
-      RouteState.DIALOG,
-      RouteState.SECTION,
-      RouteState.SUBPAGE,
-      RouteState.TOP_LEVEL,
-    ]);
-
-    return new Map([
-      [RouteState.INITIAL, allStates],
-      [
-        RouteState.DIALOG,
-        new Set([
-          RouteState.SECTION,
-          RouteState.SUBPAGE,
-          RouteState.TOP_LEVEL,
-        ]),
-      ],
-      [RouteState.SECTION, allStates],
-      [RouteState.SUBPAGE, allStates],
-      [RouteState.TOP_LEVEL, allStates],
-    ]);
-  })(),
-
-  /** @override */
-  attached() {
-    this.scroller = this.domHost ? this.domHost.parentNode : document.body;
-  },
-
-  /**
-   * Method to be defined by users of MainPageBehavior.
-   * @param {!Route} route
-   * @return {boolean} Whether the given route is part of |this| page.
-   */
-  containsRoute(route) {
-    return false;
-  },
-
-  /**
-   * @param {!Route} route
-   * @return {boolean}
-   * @private
-   */
-  shouldExpandAdvanced_(route) {
-    const routes =
-        /** @type {!MinimumRoutes} */ (Router.getInstance().getRoutes());
-    return (this.tagName === 'OS-SETTINGS-PAGE') && routes.ADVANCED &&
-        routes.ADVANCED.contains(route);
-  },
-
-  /**
-   * Finds the settings section corresponding to the given route. If the
-   * section is lazily loaded it force-renders it.
-   * Note: If the section resides within "advanced" settings, a
-   * 'hide-container' event is fired (necessary to avoid flashing). Callers
-   * are responsible for firing a 'show-container' event.
-   * @param {!Route} route
-   * @return {!Promise<!HTMLElement>}
-   * @private
-   */
-  ensureSectionForRoute_(route) {
-    const section = this.getSection(route.section);
-    if (section !== null) {
-      return Promise.resolve(section);
-    }
-
-    // The function to use to wait for <dom-if>s to render.
-    const waitFn = beforeNextRender.bind(null, this);
-
-    return new Promise(resolve => {
-      if (this.shouldExpandAdvanced_(route)) {
-        this.fire('hide-container');
-        waitFn(() => {
-          this.$$('#advancedPageTemplate').get().then(() => {
-            resolve(this.getSection(route.section));
-          });
-        });
-      } else {
-        waitFn(() => {
-          resolve(this.getSection(route.section));
-        });
-      }
-    });
-  },
-
-  /**
-   * @param {!Route} route
-   * @private
-   */
-  enterSubpage_(route) {
-    this.lastScrollTop_ = this.scroller.scrollTop;
-    this.scroller.scrollTop = 0;
-    this.classList.add('showing-subpage');
-    this.fire('subpage-expand');
-
-    // Explicitly load the lazy_load module, since all subpages reside in
-    // the lazy loaded module.
-    ensureLazyLoaded();
-
-    this.ensureSectionForRoute_(route).then(section => {
-      section.classList.add('expanded');
-      // Fire event used by a11y tests only.
-      this.fire('settings-section-expanded');
-
-      this.fire('show-container');
-    });
-  },
-
-  /**
-   * @param {!Route} oldRoute
-   * @return {!Promise<void>}
-   * @private
-   */
-  enterMainPage_(oldRoute) {
-    const oldSection = this.getSection(oldRoute.section);
-    oldSection.classList.remove('expanded');
-    this.classList.remove('showing-subpage');
-    return new Promise((res, rej) => {
-      requestAnimationFrame(() => {
-        if (Router.getInstance().lastRouteChangeWasPopstate()) {
-          this.scroller.scrollTop = this.lastScrollTop_;
-        }
-        this.fire('showing-main-page');
-        res();
-      });
-    });
-  },
-
-  /**
-   * @param {!Route} route
-   * @private
-   */
-  scrollToSection_(route) {
-    this.ensureSectionForRoute_(route).then(section => {
-      this.fire('showing-section', section);
-      this.fire('show-container');
-    });
-  },
-
-  /**
-   * Detects which state transition is appropriate for the given new/old
-   * routes.
-   * @param {!Route} newRoute
-   * @param {!Route=} oldRoute
-   * @private
-   */
-  getStateTransition_(newRoute, oldRoute) {
-    const containsNew = this.containsRoute(newRoute);
-    const containsOld = this.containsRoute(oldRoute);
-
-    if (!containsNew && !containsOld) {
-      // Nothing to do, since none of the old/new routes belong to this page.
-      return null;
-    }
-
-    // Case where going from |this| page to an unrelated page. For example:
-    //  |this| is os-settings-page AND
-    //  oldRoute is /searchEngines AND
-    //  newRoute is /help.
-    if (containsOld && !containsNew) {
-      return [classifyRoute(oldRoute), RouteState.TOP_LEVEL];
-    }
-
-    // Case where return from an unrelated page to |this| page. For example:
-    //  |this| is os-settings-page AND
-    //  oldRoute is /help AND
-    //  newRoute is /searchEngines
-    if (!containsOld && containsNew) {
-      return [RouteState.TOP_LEVEL, classifyRoute(newRoute)];
-    }
-
-    // Case where transitioning between routes that both belong to |this|
-    // page.
-    return [classifyRoute(oldRoute), classifyRoute(newRoute)];
-  },
-
-  /**
-   * @param {!Route} newRoute
-   * @param {!Route=} oldRoute
-   */
-  currentRouteChanged(newRoute, oldRoute) {
-    const transition = this.getStateTransition_(newRoute, oldRoute);
-    if (transition === null) {
-      return;
-    }
-
-    const oldState = transition[0];
-    const newState = transition[1];
-    assert(this.validTransitions_.get(oldState).has(newState));
-
-    if (oldState === RouteState.TOP_LEVEL) {
-      if (newState === RouteState.SECTION) {
-        this.scrollToSection_(newRoute);
-      } else if (newState === RouteState.SUBPAGE) {
-        this.enterSubpage_(newRoute);
-      }
-      // Nothing to do here for the case of RouteState.DIALOG or TOP_LEVEL.
-      // The latter happens when navigating from '/?search=foo' to '/'
-      // (clearing search results).
-      return;
-    }
-
-    if (oldState === RouteState.SECTION) {
-      if (newState === RouteState.SECTION) {
-        this.scrollToSection_(newRoute);
-      } else if (newState === RouteState.SUBPAGE) {
-        this.enterSubpage_(newRoute);
-      } else if (newState === RouteState.TOP_LEVEL) {
-        this.scroller.scrollTop = 0;
-      }
-      // Nothing to do here for the case of RouteState.DIALOG.
-      return;
-    }
-
-    if (oldState === RouteState.SUBPAGE) {
-      if (newState === RouteState.SECTION) {
-        this.enterMainPage_(oldRoute);
-
-        // Scroll to the corresponding section, only if the user explicitly
-        // navigated to a section (via the menu).
-        if (!Router.getInstance().lastRouteChangeWasPopstate()) {
-          this.scrollToSection_(newRoute);
-        }
-      } else if (newState === RouteState.SUBPAGE) {
-        // Handle case where the two subpages belong to
-        // different sections, but are linked to each other. For example
-        // /storage and /accounts (in ChromeOS).
-        if (!oldRoute.contains(newRoute) && !newRoute.contains(oldRoute)) {
-          this.enterMainPage_(oldRoute).then(() => {
-            this.enterSubpage_(newRoute);
-          });
-          return;
-        }
-
-        // Handle case of subpage to sub-subpage navigation.
-        if (oldRoute.contains(newRoute)) {
-          this.scroller.scrollTop = 0;
-          return;
-        }
-        // When going from a sub-subpage to its parent subpage, scroll
-        // position is automatically restored, because we focus the
-        // sub-subpage entry point.
-      } else if (newState === RouteState.TOP_LEVEL) {
-        this.enterMainPage_(oldRoute);
-      } else if (newState === RouteState.DIALOG) {
-        // The only known case currently for such a transition is from
-        // /storage to /clearBrowserData.
-        this.enterMainPage_(oldRoute);
-      }
-      return;
-    }
-
-    if (oldState === RouteState.INITIAL) {
-      if (newState === RouteState.SECTION) {
-        this.scrollToSection_(newRoute);
-      } else if (newState === RouteState.SUBPAGE) {
-        this.enterSubpage_(newRoute);
-      }
-      // Nothing to do here for the case of RouteState.DIALOG and TOP_LEVEL.
-      return;
-    }
-
-    if (oldState === RouteState.DIALOG) {
-      if (newState === RouteState.SUBPAGE) {
-        // The only known case currently for such a transition is from
-        // /clearBrowserData back to /storage.
-        this.enterSubpage_(newRoute);
-      }
-      // Nothing to do for all other cases.
-    }
-  },
-
-  /**
-   * TODO(dpapad): Rename this to |querySection| to distinguish it from
-   * ensureSectionForRoute_() which force-renders the section as needed.
-   * Helper function to get a section from the local DOM.
-   * @param {string} section Section name of the element to get.
-   * @return {?HTMLElement}
-   */
-  getSection(section) {
-    if (!section) {
-      return null;
-    }
-    return /** @type {?HTMLElement} */ (
-        this.$$(`settings-section[section="${section}"]`));
-  },
-};
-
-/** @interface */
-export class MainPageBehaviorInterface {
-  constructor() {
-    /** @type {?HTMLElement} */
-    this.scroller;
-  }
-
-  /**
-   * @param {!Route} route
-   * @return {boolean} Whether the given route is part of |this| page.
-   */
-  containsRoute(route) {}
-
-  /**
-   * @param {!Route} newRoute
-   * @param {!Route=} oldRoute
-   */
-  currentRouteChanged(newRoute, oldRoute) {}
-
-  /**
-   * @param {string} section Section name of the element to get.
-   * @return {?HTMLElement}
-   */
-  getSection(section) {}
-}
diff --git a/chrome/browser/resources/settings/chromeos/main_page_mixin.ts b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
new file mode 100644
index 0000000..823c949d
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
@@ -0,0 +1,344 @@
+// Copyright 2016 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {beforeNextRender, dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {MinimumRoutes, Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+
+import {castExists} from './assert_extras.js';
+import {ensureLazyLoaded} from './ensure_lazy_loaded.js';
+import {SettingsIdleLoadElement} from './os_settings_page/settings_idle_load.js';
+
+/**
+ * A categorization of every possible Settings URL, necessary for implementing
+ * a finite state machine.
+ */
+enum RouteState {
+  // Initial state before anything has loaded yet.
+  INITIAL = 'initial',
+  // A dialog that has a dedicated URL (e.g. /importData).
+  DIALOG = 'dialog',
+  // A section (basically a scroll position within the top level page, e.g,
+  // /appearance.
+  SECTION = 'section',
+  // A subpage, or sub-subpage e.g, /searchEngins.
+  SUBPAGE = 'subpage',
+  // The top level Settings page, '/'.
+  TOP_LEVEL = 'top-level',
+}
+
+function classifyRoute(route: Route|undefined): RouteState {
+  if (!route) {
+    return RouteState.INITIAL;
+  }
+  const routes = Router.getInstance().getRoutes() as MinimumRoutes;
+  if (route === routes.BASIC || route === routes.ABOUT) {
+    return RouteState.TOP_LEVEL;
+  }
+  if (route.isSubpage()) {
+    return RouteState.SUBPAGE;
+  }
+  if (route.isNavigableDialog) {
+    return RouteState.DIALOG;
+  }
+  return RouteState.SECTION;
+}
+
+const ALL_STATES = new Set([
+  RouteState.DIALOG,
+  RouteState.SECTION,
+  RouteState.SUBPAGE,
+  RouteState.TOP_LEVEL,
+]);
+
+/**
+ * A map holding all valid state transitions.
+ */
+const VALID_TRANSITIONS = new Map([
+  [RouteState.INITIAL, ALL_STATES],
+  [
+    RouteState.DIALOG,
+    new Set([
+      RouteState.SECTION,
+      RouteState.SUBPAGE,
+      RouteState.TOP_LEVEL,
+    ]),
+  ],
+  [RouteState.SECTION, ALL_STATES],
+  [RouteState.SUBPAGE, ALL_STATES],
+  [RouteState.TOP_LEVEL, ALL_STATES],
+]);
+
+type Constructor<T> = new (...args: any[]) => T;
+
+export interface MainPageMixinInterface extends RouteObserverMixinInterface {
+  containsRoute(route: Route|undefined): boolean;
+  currentRouteChanged(newRoute: Route, oldRoute?: Route): void;
+  querySection(section: string): HTMLElement|null;
+  loadAdvancedPage(): Promise<Element>;
+}
+
+/**
+ * Responds to route changes by expanding, collapsing, or scrolling to
+ * sections on the page. Expanded sections take up the full height of the
+ * container. At most one section should be expanded at any given time.
+ */
+export const MainPageMixin = dedupingMixin(
+    <T extends Constructor<PolymerElement>>(superClass: T): T&
+    Constructor<MainPageMixinInterface> => {
+      const superclassBase = RouteObserverMixin(superClass) as T &
+          Constructor<RouteObserverMixinInterface>;
+
+      class MainPageMixinInternal extends superclassBase implements
+          MainPageMixinInterface {
+        private lastScrollTop_: number = 0;
+
+        private get scroller_(): HTMLElement {
+          const hostEl = (this.getRootNode() as ShadowRoot).host;
+          return castExists(hostEl ? hostEl.parentElement : document.body);
+        }
+
+        /**
+         * Method to be overridden by users of MainPageMixin.
+         * @return Whether the given route is part of |this| page.
+         */
+        containsRoute(_route: Route|undefined): boolean {
+          assertNotReached();
+        }
+
+        private shouldExpandAdvanced_(route: Route): boolean {
+          const routes = Router.getInstance().getRoutes() as MinimumRoutes;
+          return (this.tagName === 'OS-SETTINGS-PAGE') && routes.ADVANCED &&
+              routes.ADVANCED.contains(route);
+        }
+
+        loadAdvancedPage(): Promise<Element> {
+          return this.shadowRoot!
+              .querySelector<SettingsIdleLoadElement>(
+                  '#advancedPageTemplate')!.get();
+        }
+
+        /**
+         * Finds the settings section corresponding to the given route. If the
+         * section is lazily loaded it force-renders it.
+         * Note: If the section resides within "advanced" settings, a
+         * 'hide-container' event is fired (necessary to avoid flashing).
+         * Callers are responsible for firing a 'show-container' event.
+         */
+        private ensureSectionForRoute_(route: Route): Promise<HTMLElement> {
+          const section = this.querySection(route.section);
+          if (section) {
+            return Promise.resolve(section);
+          }
+
+          // The function to use to wait for <dom-if>s to render.
+          const waitFn = beforeNextRender.bind(null, this);
+
+          return new Promise(resolve => {
+            if (this.shouldExpandAdvanced_(route)) {
+              this.dispatchCustomEvent_('hide-container');
+              waitFn(async () => {
+                await this.loadAdvancedPage();
+                resolve(castExists(this.querySection(route.section)));
+              });
+            } else {
+              waitFn(() => {
+                resolve(castExists(this.querySection(route.section)));
+              });
+            }
+          });
+        }
+
+        private async enterSubpage_(route: Route) {
+          this.lastScrollTop_ = this.scroller_.scrollTop;
+          this.scroller_.scrollTop = 0;
+          this.classList.add('showing-subpage');
+          this.dispatchCustomEvent_('subpage-expand');
+
+          // Explicitly load the lazy_load module, since all subpages reside in
+          // the lazy loaded module.
+          ensureLazyLoaded();
+
+          const section = await this.ensureSectionForRoute_(route);
+          section.classList.add('expanded');
+          // Fire event used by a11y tests only.
+          this.dispatchCustomEvent_('settings-section-expanded');
+          this.dispatchCustomEvent_('show-container');
+        }
+
+        private enterMainPage_(oldRoute: Route): Promise<void> {
+          const oldSection = castExists(this.querySection(oldRoute.section));
+          oldSection.classList.remove('expanded');
+          this.classList.remove('showing-subpage');
+          return new Promise((resolve) => {
+            requestAnimationFrame(() => {
+              if (Router.getInstance().lastRouteChangeWasPopstate()) {
+                this.scroller_.scrollTop = this.lastScrollTop_;
+              }
+              this.dispatchCustomEvent_('showing-main-page');
+              resolve();
+            });
+          });
+        }
+
+        private async scrollToSection_(route: Route) {
+          const section = await this.ensureSectionForRoute_(route);
+          this.dispatchCustomEvent_('showing-section', {detail: section});
+          this.dispatchCustomEvent_('show-container');
+        }
+
+        /**
+         * Detects which state transition is appropriate for the given new/old
+         * routes.
+         */
+        private getStateTransition_(newRoute: Route, oldRoute?: Route) {
+          const containsNew = this.containsRoute(newRoute);
+          const containsOld = this.containsRoute(oldRoute);
+
+          if (!containsNew && !containsOld) {
+            // Nothing to do, since none of the old/new routes belong to this
+            // page.
+            return null;
+          }
+
+          // Case where going from |this| page to an unrelated page.
+          // For example:
+          //  |this| is os-settings-page AND
+          //  oldRoute is /searchEngines AND
+          //  newRoute is /help.
+          if (containsOld && !containsNew) {
+            return [classifyRoute(oldRoute), RouteState.TOP_LEVEL];
+          }
+
+          // Case where return from an unrelated page to |this| page.
+          // For example:
+          //  |this| is os-settings-page AND
+          //  oldRoute is /help AND
+          //  newRoute is /searchEngines
+          if (!containsOld && containsNew) {
+            return [RouteState.TOP_LEVEL, classifyRoute(newRoute)];
+          }
+
+          // Case where transitioning between routes that both belong to |this|
+          // page.
+          return [classifyRoute(oldRoute), classifyRoute(newRoute)];
+        }
+
+        override currentRouteChanged(newRoute: Route, oldRoute?: Route) {
+          const transition = this.getStateTransition_(newRoute, oldRoute);
+          if (transition === null) {
+            return;
+          }
+
+          const oldState = transition[0];
+          const newState = transition[1];
+          assert(VALID_TRANSITIONS.get(oldState)!.has(newState));
+
+          if (oldState === RouteState.TOP_LEVEL) {
+            if (newState === RouteState.SECTION) {
+              this.scrollToSection_(newRoute);
+            } else if (newState === RouteState.SUBPAGE) {
+              this.enterSubpage_(newRoute);
+            }
+            // Nothing to do here for the case of RouteState.DIALOG or
+            // TOP_LEVEL. The latter happens when navigating from '/?search=foo'
+            // to '/' (clearing search results).
+            return;
+          }
+
+          if (oldState === RouteState.SECTION) {
+            if (newState === RouteState.SECTION) {
+              this.scrollToSection_(newRoute);
+            } else if (newState === RouteState.SUBPAGE) {
+              this.enterSubpage_(newRoute);
+            } else if (newState === RouteState.TOP_LEVEL) {
+              this.scroller_.scrollTop = 0;
+            }
+            // Nothing to do here for the case of RouteState.DIALOG.
+            return;
+          }
+
+          if (oldState === RouteState.SUBPAGE) {
+            assert(oldRoute);
+            if (newState === RouteState.SECTION) {
+              this.enterMainPage_(oldRoute);
+
+              // Scroll to the corresponding section, only if the user
+              // explicitly navigated to a section (via the menu).
+              if (!Router.getInstance().lastRouteChangeWasPopstate()) {
+                this.scrollToSection_(newRoute);
+              }
+            } else if (newState === RouteState.SUBPAGE) {
+              // Handle case where the two subpages belong to
+              // different sections, but are linked to each other. For example
+              // /storage and /accounts (in ChromeOS).
+              if (!oldRoute.contains(newRoute) &&
+                  !newRoute.contains(oldRoute)) {
+                this.enterMainPage_(oldRoute).then(() => {
+                  this.enterSubpage_(newRoute);
+                });
+                return;
+              }
+
+              // Handle case of subpage to sub-subpage navigation.
+              if (oldRoute.contains(newRoute)) {
+                this.scroller_.scrollTop = 0;
+                return;
+              }
+              // When going from a sub-subpage to its parent subpage, scroll
+              // position is automatically restored, because we focus the
+              // sub-subpage entry point.
+            } else if (newState === RouteState.TOP_LEVEL) {
+              this.enterMainPage_(oldRoute);
+            } else if (newState === RouteState.DIALOG) {
+              // The only known case currently for such a transition is from
+              // /storage to /clearBrowserData.
+              this.enterMainPage_(oldRoute);
+            }
+            return;
+          }
+
+          if (oldState === RouteState.INITIAL) {
+            if (newState === RouteState.SECTION) {
+              this.scrollToSection_(newRoute);
+            } else if (newState === RouteState.SUBPAGE) {
+              this.enterSubpage_(newRoute);
+            }
+            // Nothing to do here for the case of RouteState.DIALOG and
+            // TOP_LEVEL.
+            return;
+          }
+
+          if (oldState === RouteState.DIALOG) {
+            if (newState === RouteState.SUBPAGE) {
+              // The only known case currently for such a transition is from
+              // /clearBrowserData back to /storage.
+              this.enterSubpage_(newRoute);
+            }
+            // Nothing to do for all other cases.
+          }
+        }
+
+        /**
+         * Helper function to get a section from the local DOM.
+         */
+        querySection(section: string): HTMLElement|null {
+          if (!section) {
+            return null;
+          }
+          return this.shadowRoot!.querySelector(
+              `settings-section[section="${section}"]`);
+        }
+
+        private dispatchCustomEvent_(
+            name: string, options?: CustomEventInit<unknown>) {
+          const event = new CustomEvent(
+              name, {bubbles: true, composed: true, ...options});
+          this.dispatchEvent(event);
+        }
+      }
+
+      return MainPageMixinInternal;
+    });
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
index f763801..ba54df9a 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
@@ -162,11 +162,6 @@
 
   static get properties() {
     return {
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       autoScanSpeedRangeMs_: {
         type: Array,
         value: [],
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
index bc286bf4..d0681e9 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
@@ -83,11 +83,6 @@
 
   static get properties() {
     return {
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       selectAssignments_: {
         type: Array,
         value: [],
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
index 53094f3..1cf29ada 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
@@ -66,12 +66,6 @@
 
   static get properties() {
     return {
-      /** Preferences state. */
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
       versionInfo_: Object,
 
       channelInfo_: Object,
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
index 07ffeca1..fa621ae 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
@@ -37,10 +37,9 @@
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {Route, Router} from '../../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
-import {MainPageBehavior, MainPageBehaviorInterface} from '../main_page_behavior.js';
+import {MainPageMixin, MainPageMixinInterface} from '../main_page_mixin.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, RegulatoryInfo, TpmFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent} from './about_page_browser_proxy.js';
 import {getTemplate} from './os_about_page.html.js';
@@ -62,13 +61,11 @@
     mixinBehaviors(
         [
           DeepLinkingBehavior,
-          MainPageBehavior,
-          RouteObserverBehavior,
         ],
-        I18nMixin(WebUiListenerMixin(PolymerElement))) as {
-      new (): PolymerElement & DeepLinkingBehaviorInterface &
-          WebUiListenerMixinInterface & MainPageBehaviorInterface &
-          RouteObserverBehaviorInterface & I18nMixinInterface,
+        MainPageMixin(I18nMixin(WebUiListenerMixin(PolymerElement)))) as {
+      new (): PolymerElement & WebUiListenerMixinInterface &
+          I18nMixinInterface & MainPageMixinInterface &
+          DeepLinkingBehaviorInterface,
     };
 
 class OsSettingsAboutPageElement extends OsSettingsAboutPageBaseElement {
@@ -337,12 +334,9 @@
   }
 
   override currentRouteChanged(newRoute: Route, oldRoute?: Route) {
-    // super.currentRouteChanged() does not produce desired results since
-    // RouteObserverBehavior has higher precedence than MainPageBehavior given
-    // this element's behavior list order. In order to trigger the
-    // MainPageBehavior method, we must directly call it.
+    // MainPageMixin#currentRouteChanged() should be the super class method
     // See https://crbug.com/1324103 for more details.
-    MainPageBehavior.currentRouteChanged.call(this, newRoute, oldRoute);
+    super.currentRouteChanged(newRoute, oldRoute);
 
     // Does not apply to this page.
     if (newRoute !== routes.ABOUT_ABOUT) {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 13173cd2..83f400e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -220,7 +220,7 @@
   "chromeos/internet_page/internet_page_browser_proxy.js",
   "chromeos/kerberos_page/kerberos_accounts_browser_proxy.ts",
   "chromeos/lazy_load.js",
-  "chromeos/main_page_behavior.js",
+  "chromeos/main_page_mixin.ts",
   "chromeos/metrics_recorder.js",
   "chromeos/multidevice_page/multidevice_browser_proxy.js",
   "chromeos/multidevice_page/multidevice_constants.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
index 601becef..9fcceae6 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
@@ -28,28 +28,22 @@
 import '../os_bluetooth_page/os_bluetooth_page.js';
 import '../os_settings_icons.html.js';
 
-import {WebUiListenerMixin, WebUiListenerMixinInterface} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
+import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {beforeNextRender, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {beforeNextRender, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Route, Router} from '../../router.js';
 import {castExists} from '../assert_extras.js';
-import {MainPageBehavior, MainPageBehaviorInterface} from '../main_page_behavior.js';
+import {MainPageMixin} from '../main_page_mixin.js';
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from '../os_apps_page/android_apps_browser_proxy.js';
 import {OSPageVisibility} from '../os_page_visibility.js';
 import {routes} from '../os_route.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
 import {getTemplate} from './os_settings_page.html.js';
-import {SettingsIdleLoadElement} from './settings_idle_load.js';
 
-const OsSettingsPageElementBase = mixinBehaviors(
-                                      [MainPageBehavior, RouteObserverBehavior],
-                                      WebUiListenerMixin(PolymerElement)) as {
-  new (): PolymerElement & WebUiListenerMixinInterface &
-      MainPageBehaviorInterface & RouteObserverBehaviorInterface,
-};
+const OsSettingsPageElementBase =
+    MainPageMixin(WebUiListenerMixin(PolymerElement));
 
 class OsSettingsPageElement extends OsSettingsPageElementBase {
   static get is() {
@@ -212,7 +206,8 @@
       assert(!this.hasExpandedSection_);
     }
 
-    MainPageBehavior.currentRouteChanged.call(this, newRoute, oldRoute);
+    // MainPageMixin#currentRouteChanged() should be the super class method
+    super.currentRouteChanged(newRoute, oldRoute);
   }
 
   override containsRoute(route: Route) {
@@ -252,11 +247,6 @@
     this.hasExpandedSection_ = true;
   }
 
-  private getAdvancedPageTemplate_(): SettingsIdleLoadElement {
-    return castExists(this.shadowRoot!.querySelector<SettingsIdleLoadElement>(
-        '#advancedPageTemplate'));
-  }
-
   /**
    * Render the advanced page now (don't wait for idle).
    */
@@ -268,7 +258,7 @@
     // In Polymer2, async() does not wait long enough for layout to complete.
     // beforeNextRender() must be used instead.
     beforeNextRender(this, () => {
-      this.getAdvancedPageTemplate_().get();
+      this.loadAdvancedPage();
     });
   }
 
@@ -284,7 +274,7 @@
     if (!this.advancedToggleExpanded) {
       this.advancedToggleExpanded = true;
       microTask.run(() => {
-        this.getAdvancedPageTemplate_().get().then(() => {
+        this.loadAdvancedPage().then(() => {
           const event = new CustomEvent('scroll-to-top', {
             bubbles: true,
             composed: true,
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
index 22543a4..8ab15d404 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
@@ -456,7 +456,7 @@
    */
   private onMenuClose_() {
     if (!this.getDrawer_().wasCanceled()) {
-      // If a navigation happened, MainPageBehavior#currentRouteChanged
+      // If a navigation happened, MainPageMixin#currentRouteChanged
       // handles focusing the corresponding section when we call
       // settings.NavigateTo().
       this.navigateToActiveRoute_();
diff --git a/chrome/browser/resources/settings/router.js b/chrome/browser/resources/settings/router.js
index 91631c4..c45a047 100644
--- a/chrome/browser/resources/settings/router.js
+++ b/chrome/browser/resources/settings/router.js
@@ -231,6 +231,11 @@
       document.title = loadTimeData.getStringF(
           'settingsAltPageTitle', this.currentRoute.title);
     } else if (
+        this.currentRoute.isNavigableDialog && this.currentRoute.parent &&
+        this.currentRoute.parent.title) {
+      document.title = loadTimeData.getStringF(
+          'settingsAltPageTitle', this.currentRoute.parent.title);
+    } else if (
         !this.currentRoute.isSubpage() &&
         !this.routes_.ABOUT.contains(this.currentRoute)) {
       document.title = loadTimeData.getString('settings');
diff --git a/chrome/browser/resources/side_panel/BUILD.gn b/chrome/browser/resources/side_panel/BUILD.gn
index 28c4ba16..3809b5d 100644
--- a/chrome/browser/resources/side_panel/BUILD.gn
+++ b/chrome/browser/resources/side_panel/BUILD.gn
@@ -49,33 +49,27 @@
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/side_panel"
 
 copy("copy_mojo_bookmarks") {
-  deps = [
-    "//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings_webui_js",
-  ]
+  deps = [ "//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings_js__generator" ]
   sources = [ "$mojo_root_folder/bookmarks/bookmarks.mojom-webui.js" ]
   outputs =
       [ "$target_gen_dir/$preprocess_folder/bookmarks/{{source_file_part}}" ]
 }
 
 copy("copy_mojo_reading_list") {
-  deps = [
-    "//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings_webui_js",
-  ]
+  deps = [ "//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings_js__generator" ]
   sources = [ "$mojo_root_folder/reading_list/reading_list.mojom-webui.js" ]
   outputs =
       [ "$target_gen_dir/$preprocess_folder/reading_list/{{source_file_part}}" ]
 }
 
 copy("copy_mojo_shopping_list") {
-  deps = [ "//components/commerce/core/mojom:mojo_bindings_webui_js" ]
+  deps = [ "//components/commerce/core/mojom:mojo_bindings_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/components/commerce/core/mojom/shopping_list.mojom-webui.js" ]
   outputs = [ "$target_gen_dir/$preprocess_folder/bookmarks/commerce/{{source_file_part}}" ]
 }
 
 copy("copy_mojo_user_notes") {
-  deps = [
-    "//chrome/browser/ui/webui/side_panel/user_notes:mojo_bindings_webui_js",
-  ]
+  deps = [ "//chrome/browser/ui/webui/side_panel/user_notes:mojo_bindings_js__generator" ]
   sources = [ "$mojo_root_folder/user_notes/user_notes.mojom-webui.js" ]
   outputs =
       [ "$target_gen_dir/$preprocess_folder/user_notes/{{source_file_part}}" ]
diff --git a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
index 9c98b1f..ca106b9 100644
--- a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
@@ -14,7 +14,6 @@
   bookmarkCurrentTab(): void;
   cutBookmark(id: string): void;
   copyBookmark(id: string): Promise<void>;
-  getTopLevelBookmarks(): Promise<chrome.bookmarks.BookmarkTreeNode[]>;
   getFolders(): Promise<chrome.bookmarks.BookmarkTreeNode[]>;
   openBookmark(
       id: string, depth: number, clickModifiers: ClickModifiers,
@@ -58,23 +57,6 @@
     });
   }
 
-  getTopLevelBookmarks() {
-    return new Promise<chrome.bookmarks.BookmarkTreeNode[]>(
-        resolve => chrome.bookmarks.getTree(results => {
-          if (results[0] && results[0].children) {
-            let allBookmarks: chrome.bookmarks.BookmarkTreeNode[] = [];
-            for (const child of results[0].children) {
-              if (child.children) {
-                allBookmarks = allBookmarks.concat(child.children);
-              }
-            }
-            resolve(allBookmarks);
-            return;
-          }
-          resolve([]);
-        }));
-  }
-
   getFolders() {
     return new Promise<chrome.bookmarks.BookmarkTreeNode[]>(
         resolve => chrome.bookmarks.getTree(results => {
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
index fbe7ddf..9338c51 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
@@ -13,6 +13,7 @@
 import '//resources/cr_elements/icons.html.js';
 
 import {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {listenOnce} from 'chrome://resources/js/util.js';
@@ -24,6 +25,10 @@
 import {ShoppingListApiProxy, ShoppingListApiProxyImpl} from './commerce/shopping_list_api_proxy.js';
 import {getTemplate} from './power_bookmarks_list.html.js';
 
+function getBookmarkName(bookmark: chrome.bookmarks.BookmarkTreeNode): string {
+  return bookmark.title || bookmark.url || '';
+}
+
 interface Label {
   label: string;
   icon: string;
@@ -48,7 +53,7 @@
 
   static get properties() {
     return {
-      topLevelBookmarks_: {
+      folders_: {
         type: Array,
         value: () => [],
       },
@@ -100,17 +105,18 @@
 
   static get observers() {
     return [
-      'updateShownBookmarks_(topLevelBookmarks_, ' +
+      'updateShownBookmarks_(folders_.*, ' +
           'activeFolderPath_.*, labels_.*, activeSortIndex_)',
     ];
   }
 
-  private topLevelBookmarks_: chrome.bookmarks.BookmarkTreeNode[];
+  private folders_: chrome.bookmarks.BookmarkTreeNode[];
   private shownBookmarks_: chrome.bookmarks.BookmarkTreeNode[];
   private bookmarksApi_: BookmarksApiProxy =
       BookmarksApiProxyImpl.getInstance();
   private shoppingListApi_: ShoppingListApiProxy =
       ShoppingListApiProxyImpl.getInstance();
+  private listeners_ = new Map<string, Function>();
   private productInfos_ = new Map<string, BookmarkProductInfo>();
   private shoppingListenerIds_: number[] = [];
   private compact_: boolean;
@@ -128,11 +134,24 @@
     listenOnce(this.$.powerBookmarksContainer, 'dom-change', () => {
       setTimeout(() => this.bookmarksApi_.showUI(), 0);
     });
-    this.bookmarksApi_.getTopLevelBookmarks().then(topLevelBookmarks => {
-      this.topLevelBookmarks_ = topLevelBookmarks;
-      this.topLevelBookmarks_.forEach(bookmark => {
-        this.findBookmarkDescriptions_(bookmark);
+    this.bookmarksApi_.getFolders().then(folders => {
+      this.folders_ = folders;
+      this.folders_.forEach(bookmark => {
+        this.findBookmarkDescriptions_(bookmark, true);
       });
+      this.addListener_(
+          'onChanged',
+          (id: string, changedInfo: chrome.bookmarks.ChangeInfo) =>
+              this.onChanged_(id, changedInfo));
+      this.addListener_(
+          'onCreated',
+          (_id: string, node: chrome.bookmarks.BookmarkTreeNode) =>
+              this.onCreated_(node));
+      this.addListener_(
+          'onMoved',
+          (_id: string, movedInfo: chrome.bookmarks.MoveInfo) =>
+              this.onMoved_(movedInfo));
+      this.addListener_('onRemoved', (id: string) => this.onRemoved_(id));
     });
     this.shoppingListApi_.getAllPriceTrackedBookmarkProductInfo().then(res => {
       res.productInfos.forEach(
@@ -152,16 +171,190 @@
   }
 
   override disconnectedCallback() {
+    for (const [eventName, callback] of this.listeners_.entries()) {
+      this.bookmarksApi_.callbackRouter[eventName]!.removeListener(callback);
+    }
     this.shoppingListenerIds_.forEach(
         id => this.shoppingListApi_.getCallbackRouter().removeListener(id));
   }
 
+  private addListener_(eventName: string, callback: Function): void {
+    this.bookmarksApi_.callbackRouter[eventName]!.addListener(callback);
+    this.listeners_.set(eventName, callback);
+  }
+
   /**
-   * Assigns a text description for the given bookmark and all descendants, to
-   * be displayed following the bookmark title.
+   * Returns the index of the given node id in the currently shown bookmarks,
+   * or -1 if not shown.
    */
-  private findBookmarkDescriptions_(bookmark:
-                                        chrome.bookmarks.BookmarkTreeNode) {
+  private visibleIndex_(nodeId: string): number {
+    return this.shownBookmarks_.findIndex(b => b.id === nodeId);
+  }
+
+  /**
+   * Returns true if the given node is either the current active folder or a
+   * root folder while the all bookmarks list is shown.
+   */
+  private visibleParent_(parent: chrome.bookmarks.BookmarkTreeNode): boolean {
+    const activeFolder = this.getActiveFolder_();
+    return (!activeFolder && parent.parentId === '0') ||
+        parent === activeFolder;
+  }
+
+  private onChanged_(id: string, changedInfo: chrome.bookmarks.ChangeInfo) {
+    const path = this.findPathToId_(id);
+    const bookmark = path[path.length - 1]!;
+    Object.assign(bookmark, changedInfo);
+    this.findBookmarkDescriptions_(bookmark, false);
+    Object.keys(changedInfo).forEach(key => {
+      const visibleIndex = this.visibleIndex_(id);
+      if (visibleIndex > -1) {
+        this.notifyPath(`shownBookmarks_.${visibleIndex}.${key}`);
+      }
+    });
+  }
+
+  private onCreated_(node: chrome.bookmarks.BookmarkTreeNode) {
+    const pathToParent = this.findPathToId_(node.parentId as string);
+    const parent = pathToParent[pathToParent.length - 1]!;
+    if (!parent.children) {
+      // Newly created folders in this session may not have an array of
+      // children yet, so create an empty one.
+      parent.children = [];
+    }
+    parent.children!.splice(node.index!, 0, node);
+    if (this.visibleParent_(parent)) {
+      this.shownBookmarks_.unshift(node);
+      this.sortBookmarks_(this.shownBookmarks_);
+      this.shownBookmarks_ = this.shownBookmarks_.slice();
+      getAnnouncerInstance().announce(
+          loadTimeData.getStringF('bookmarkCreated', getBookmarkName(node)));
+    }
+    this.findBookmarkDescriptions_(parent, false);
+    this.findBookmarkDescriptions_(node, false);
+  }
+
+  private onMoved_(movedInfo: chrome.bookmarks.MoveInfo) {
+    // Get old path and remove node from oldParent at oldIndex.
+    const oldParentPath = this.findPathToId_(movedInfo.oldParentId);
+    const oldParent = oldParentPath[oldParentPath.length - 1]!;
+    const movedNode = oldParent!.children![movedInfo.oldIndex]!;
+    Object.assign(
+        movedNode, {index: movedInfo.index, parentId: movedInfo.parentId});
+    oldParent.children!.splice(movedInfo.oldIndex, 1);
+
+    // Get new parent's path and add the node to the new parent at index.
+    const newParentPath = this.findPathToId_(movedInfo.parentId);
+    const newParent = newParentPath[newParentPath.length - 1]!;
+    if (!newParent.children) {
+      newParent.children = [];
+    }
+    newParent.children!.splice(movedInfo.index, 0, movedNode);
+
+    const shouldUpdateUIAdded = this.visibleParent_(newParent);
+    const shouldUpdateUIRemoved = this.visibleParent_(oldParent);
+    const shouldUpdateUIReordered =
+        shouldUpdateUIAdded && shouldUpdateUIRemoved;
+
+    if (shouldUpdateUIReordered) {
+      getAnnouncerInstance().announce(loadTimeData.getStringF(
+          'bookmarkReordered', getBookmarkName(movedNode)));
+    } else if (shouldUpdateUIAdded) {
+      this.shownBookmarks_.unshift(movedNode);
+      this.sortBookmarks_(this.shownBookmarks_);
+      this.shownBookmarks_ = this.shownBookmarks_.slice();
+      getAnnouncerInstance().announce(loadTimeData.getStringF(
+          'bookmarkMoved', getBookmarkName(movedNode),
+          getBookmarkName(newParent)));
+    } else if (shouldUpdateUIRemoved) {
+      this.splice('shownBookmarks_', this.visibleIndex_(movedNode.id), 1);
+      getAnnouncerInstance().announce(loadTimeData.getStringF(
+          'bookmarkMoved', getBookmarkName(movedNode),
+          getBookmarkName(newParent)));
+    }
+
+    if (movedInfo.oldParentId !== movedInfo.parentId) {
+      this.findBookmarkDescriptions_(oldParent, false);
+      this.findBookmarkDescriptions_(newParent, false);
+    }
+  }
+
+  private onRemoved_(id: string) {
+    const oldPath = this.findPathToId_(id);
+    const removedNode = oldPath.pop()!;
+    const oldParent = oldPath[oldPath.length - 1]!;
+    oldParent.children!.splice(oldParent.children!.indexOf(removedNode), 1);
+    this.productInfos_.delete(id);
+    const visibleIndex = this.visibleIndex_(id);
+    if (visibleIndex > -1) {
+      this.splice('shownBookmarks_', visibleIndex, 1);
+      getAnnouncerInstance().announce(loadTimeData.getStringF(
+          'bookmarkDeleted', getBookmarkName(removedNode)));
+    }
+    this.findBookmarkDescriptions_(oldParent, false);
+  }
+
+  /**
+   * Finds the node within all bookmarks and returns the path to the node in
+   * the tree.
+   */
+  private findPathToId_(id: string): chrome.bookmarks.BookmarkTreeNode[] {
+    const path: chrome.bookmarks.BookmarkTreeNode[] = [];
+
+    function findPathByIdInternal(
+        id: string, node: chrome.bookmarks.BookmarkTreeNode) {
+      if (node.id === id) {
+        path.push(node);
+        return true;
+      }
+
+      if (!node.children) {
+        return false;
+      }
+
+      path.push(node);
+      const foundInChildren =
+          node.children.some(child => findPathByIdInternal(id, child));
+      if (!foundInChildren) {
+        path.pop();
+      }
+
+      return foundInChildren;
+    }
+
+    this.folders_.some(bookmark => findPathByIdInternal(id, bookmark));
+    return path;
+  }
+
+  private getActiveFolder_(): chrome.bookmarks.BookmarkTreeNode|undefined {
+    if (this.activeFolderPath_.length) {
+      return this.activeFolderPath_[this.activeFolderPath_.length - 1];
+    }
+    return undefined;
+  }
+
+  /**
+   * Reduces an array of nodes to a string to notify Polymer of changes to the
+   * nested array.
+   */
+  private getPathString_(path: chrome.bookmarks.BookmarkTreeNode[]): string {
+    return path.reduce((reducedString, pathItem, index) => {
+      if (index === 0) {
+        return `folders_.${this.folders_.indexOf(pathItem)}`;
+      }
+
+      const parent = path[index - 1];
+      return `${reducedString}.children.${parent!.children!.indexOf(pathItem)}`;
+    }, '');
+  }
+
+  /**
+   * Assigns a text description for the given bookmark, to be displayed
+   * following the bookmark title. Also assigns a description to all
+   * descendants if recurse is true.
+   */
+  private findBookmarkDescriptions_(
+      bookmark: chrome.bookmarks.BookmarkTreeNode, recurse: boolean) {
     if (bookmark.children) {
       PluralStringProxyImpl.getInstance()
           .getPluralString('bookmarkFolderChildCount', bookmark.children.length)
@@ -179,11 +372,17 @@
         this.set(`expandedDescriptions_.${bookmark.id}`, url.hostname);
       }
     }
-    if (bookmark.children) {
-      bookmark.children.forEach(child => this.findBookmarkDescriptions_(child));
+    if (recurse && bookmark.children) {
+      bookmark.children.forEach(
+          child => this.findBookmarkDescriptions_(child, recurse));
     }
   }
 
+  private getBookmarkName_(bookmark: chrome.bookmarks.BookmarkTreeNode):
+      string {
+    return bookmark.title || bookmark.url || '';
+  }
+
   private getBookmarkDescription_(bookmark: chrome.bookmarks.BookmarkTreeNode):
       string|undefined {
     if (this.compact_) {
@@ -194,9 +393,8 @@
   }
 
   private getFolderLabel_(): string {
-    if (this.activeFolderPath_.length) {
-      const activeFolder =
-          this.activeFolderPath_[this.activeFolderPath_.length - 1];
+    const activeFolder = this.getActiveFolder_();
+    if (activeFolder) {
       return activeFolder!.title;
     } else {
       return loadTimeData.getString('allBookmarks');
@@ -221,12 +419,15 @@
    */
   private updateShownBookmarks_() {
     let shownBookmarks;
-    const activeFolder =
-        this.activeFolderPath_[this.activeFolderPath_.length - 1];
+    const activeFolder = this.getActiveFolder_();
     if (activeFolder) {
-      shownBookmarks = activeFolder.children!;
+      shownBookmarks = activeFolder.children!.slice();
     } else {
-      shownBookmarks = this.topLevelBookmarks_;
+      let topLevelBookmarks: chrome.bookmarks.BookmarkTreeNode[] = [];
+      this.folders_.forEach(
+          folder => topLevelBookmarks =
+              topLevelBookmarks.concat(folder.children!));
+      shownBookmarks = topLevelBookmarks;
     }
     // Price tracking label
     if (this.labels_[0]!.active) {
diff --git a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
index e999a83..21d0eb46 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
+++ b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
@@ -15,6 +15,9 @@
   web_component_files = [
     "app.ts",
     "shortcuts.ts",
+    "categories.ts",
+    "appearance.ts",
+    "themes.ts",
   ]
 
   non_web_component_files = [ "customize_chrome_api_proxy.ts" ]
@@ -24,7 +27,7 @@
     html_files += [ string_replace(f, ".ts", ".html") ]
   }
 
-  mojo_files_deps = [ "//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings_webui_js" ]
+  mojo_files_deps = [ "//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom-webui.js" ]
 
   ts_composite = true
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html b/chrome/browser/resources/side_panel/customize_chrome/app.html
index 320a4704..45dca72c 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.html
@@ -1,3 +1,16 @@
-<div id="body">
+<iron-pages selected="[[page_]]" attr-for-selected="page-name">
+  <div page-name="overview" id="overviewPage">
+    <customize-chrome-appearance on-edit-theme-click="onEditThemeClick_"
+        id="appearanceElement">
+    </customize-chrome-appearance>
     <customize-chrome-shortcuts></customize-chrome-shortcuts>
-</div>
+  </div>
+  <customize-chrome-categories on-back-click="onBackClick_"
+      on-category-select="onCategorySelect_" page-name="categories"
+      id="categoriesPage">
+  </customize-chrome-categories>
+  <customize-chrome-themes on-back-click="onBackClick_"
+      on-theme-select="onThemeSelect_" page-name="themes"
+      id="themesPage">
+  </customize-chrome-themes>
+</iron-pages>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.ts b/chrome/browser/resources/side_panel/customize_chrome/app.ts
index 441da4e7..4f680062 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.ts
@@ -1,13 +1,33 @@
 // Copyright 2022 The Chromium Authors
 // 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/iron-pages/iron-pages.js';
+import './appearance.js';
+import './categories.js';
 import './shortcuts.js';
+import './themes.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './app.html.js';
+import {AppearanceElement} from './appearance.js';
+import {CategoriesElement} from './categories.js';
+import {ThemesElement} from './themes.js';
 
-export interface AppElement {}
+export enum CustomizeChromePage {
+  OVERVIEW = 'overview',
+  CATEGORIES = 'categories',
+  THEMES = 'themes',
+}
+
+export interface AppElement {
+  $: {
+    overviewPage: HTMLDivElement,
+    categoriesPage: CategoriesElement,
+    themesPage: ThemesElement,
+    appearanceElement: AppearanceElement,
+  };
+}
 
 export class AppElement extends PolymerElement {
   static get is() {
@@ -18,13 +38,46 @@
     return getTemplate();
   }
 
-
   static get properties() {
-    return {};
+    return {
+      page_: {
+        type: String,
+        value: CustomizeChromePage.OVERVIEW,
+      },
+      selectedCategory_: {
+        type: Object,
+        value: null,
+      },
+    };
   }
 
-  constructor() {
-    super();
+  private page_: CustomizeChromePage;
+  // Will make this a more specific object when the
+  // handler is updated to support collections/categories.
+  private selectedCategory_: object|null;
+
+  private onBackClick_() {
+    switch (this.page_) {
+      case CustomizeChromePage.CATEGORIES:
+        this.page_ = CustomizeChromePage.OVERVIEW;
+        break;
+      case CustomizeChromePage.THEMES:
+        this.page_ = CustomizeChromePage.CATEGORIES;
+        break;
+    }
+  }
+
+  private onEditThemeClick_() {
+    this.page_ = CustomizeChromePage.CATEGORIES;
+  }
+
+  private onCategorySelect_(event: CustomEvent<object>) {
+    this.selectedCategory_ = event.detail;
+    this.page_ = CustomizeChromePage.THEMES;
+  }
+
+  private onThemeSelect_() {
+    this.page_ = CustomizeChromePage.OVERVIEW;
   }
 }
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.html b/chrome/browser/resources/side_panel/customize_chrome/appearance.html
new file mode 100644
index 0000000..b643145
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.html
@@ -0,0 +1 @@
+<button id="editThemeButton" on-click="onEditThemeClicked_">Edit Theme</button>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.ts b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
new file mode 100644
index 0000000..04805ac
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './appearance.html.js';
+
+export interface AppearanceElement {
+  $: {
+    editThemeButton: HTMLButtonElement,
+  };
+}
+
+export class AppearanceElement extends PolymerElement {
+  static get is() {
+    return 'customize-chrome-appearance';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  private onEditThemeClicked_() {
+    this.dispatchEvent(new Event('edit-theme-click'));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'customize-chrome-appearance': AppearanceElement;
+  }
+}
+
+customElements.define(AppearanceElement.is, AppearanceElement);
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.html b/chrome/browser/resources/side_panel/customize_chrome/categories.html
new file mode 100644
index 0000000..9c5b6e4
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.html
@@ -0,0 +1,7 @@
+<button on-click="onBackClick_" id="backButton">
+  Go Back
+</button>
+<p>Category List</p>
+<button on-click="onCategoryClick_" class="category">
+  Select Category
+</button>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.ts b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
new file mode 100644
index 0000000..ea70ee77
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './categories.html.js';
+
+export interface CategoriesElement {
+  $: {
+    backButton: HTMLButtonElement,
+  };
+}
+
+export class CategoriesElement extends PolymerElement {
+  static get is() {
+    return 'customize-chrome-categories';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  private onCategoryClick_() {
+    this.dispatchEvent(new CustomEvent<object>('category-select'));
+  }
+
+  private onBackClick_() {
+    this.dispatchEvent(new Event('back-click'));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'customize-chrome-categories': CategoriesElement;
+  }
+}
+
+customElements.define(CategoriesElement.is, CategoriesElement);
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.html b/chrome/browser/resources/side_panel/customize_chrome/themes.html
new file mode 100644
index 0000000..0c0705c
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.html
@@ -0,0 +1,7 @@
+<button on-click="onBackClick_" id="backButton">
+  Go Back
+</button>
+<p>Category Theme List</p>
+<button on-click="onSelectTheme_" class="theme">
+  Select Theme
+</button>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.ts b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
new file mode 100644
index 0000000..e8c046a
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './themes.html.js';
+
+export interface ThemesElement {
+  $: {
+    backButton: HTMLButtonElement,
+  };
+}
+
+export class ThemesElement extends PolymerElement {
+  static get is() {
+    return 'customize-chrome-themes';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  private onBackClick_() {
+    this.dispatchEvent(new Event('back-click'));
+  }
+
+  private onSelectTheme_() {
+    this.dispatchEvent(new Event('theme-select'));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'customize-chrome-themes': ThemesElement;
+  }
+}
+
+customElements.define(ThemesElement.is, ThemesElement);
diff --git a/chrome/browser/resources/tab_search/BUILD.gn b/chrome/browser/resources/tab_search/BUILD.gn
index 14712820..5c57ccd1 100644
--- a/chrome/browser/resources/tab_search/BUILD.gn
+++ b/chrome/browser/resources/tab_search/BUILD.gn
@@ -45,9 +45,9 @@
   css_files = [ "tab_group_shared_vars.css" ]
 
   mojo_files_deps = [
-    "//chrome/browser/ui/webui/tab_search:mojo_bindings_webui_js",
-    "//chrome/browser/ui/webui/tabs:mojo_bindings_webui_js",
-    "//components/tab_groups/public/mojom:mojo_bindings_webui_js",
+    "//chrome/browser/ui/webui/tab_search:mojo_bindings_js__generator",
+    "//chrome/browser/ui/webui/tabs:mojo_bindings_js__generator",
+    "//components/tab_groups/public/mojom:mojo_bindings_js__generator",
   ]
   mojo_files = [
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_search/tab_search.mojom-webui.js",
diff --git a/chrome/browser/resources/tab_strip/BUILD.gn b/chrome/browser/resources/tab_strip/BUILD.gn
index 472379b1e..2a0eb34 100644
--- a/chrome/browser/resources/tab_strip/BUILD.gn
+++ b/chrome/browser/resources/tab_strip/BUILD.gn
@@ -42,8 +42,8 @@
   ]
 
   mojo_files_deps = [
-    "//chrome/browser/ui/webui/tab_strip:mojo_bindings_webui_js",
-    "//chrome/browser/ui/webui/tabs:mojo_bindings_webui_js",
+    "//chrome/browser/ui/webui/tab_strip:mojo_bindings_js__generator",
+    "//chrome/browser/ui/webui/tabs:mojo_bindings_js__generator",
   ]
   mojo_files = [
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_strip/tab_strip.mojom-webui.js",
diff --git a/chrome/browser/resources/usb_internals/BUILD.gn b/chrome/browser/resources/usb_internals/BUILD.gn
index f6d283a..8c0d498 100644
--- a/chrome/browser/resources/usb_internals/BUILD.gn
+++ b/chrome/browser/resources/usb_internals/BUILD.gn
@@ -19,9 +19,9 @@
   ]
 
   mojo_files_deps = [
-    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_webui_js",
-    "//services/device/public/mojom:usb_test_js",
-    "//services/device/public/mojom:usb_webui_js",
+    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_js__generator",
+    "//services/device/public/mojom:usb_js__generator",
+    "//services/device/public/mojom:usb_test_js__generator",
   ]
   mojo_files = [
     "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/usb_internals/usb_internals.mojom-webui.js",
diff --git a/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc b/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
index 6e7301b3..26fd100 100644
--- a/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
@@ -175,8 +175,8 @@
   const GURL page_url1("https://google.com/");
   const GURL page_url2("https://example.com/");
 
-  SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
-  ASSERT_TRUE(SetupSync(WAIT_FOR_SYNC_SETUP_TO_COMPLETE));
+  ASSERT_TRUE(SetupSync());
+  GetSyncService()->GetUserSettings()->SetEncryptionPassphrase("hunter2");
 
   ASSERT_TRUE(AddURL(/*profile=*/0, title1, page_url1));
   ASSERT_TRUE(AddURL(/*profile=*/0, title2, page_url2));
@@ -301,23 +301,22 @@
                        DoesNotLeakUnencryptedData) {
   const std::string title = "Should be encrypted";
   const GURL page_url("https://google.com/encrypted");
-  SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
   ASSERT_TRUE(SetupClients());
 
-  // Create local bookmarks before sync is enabled.
+  // Create local bookmarks before setting up sync.
+  CommittedBookmarkEntityNameObserver observer(GetFakeServer());
   ASSERT_TRUE(AddURL(/*profile=*/0, title, page_url));
 
-  CommittedBookmarkEntityNameObserver observer(GetFakeServer());
-  ASSERT_TRUE(SetupSync(WAIT_FOR_SYNC_SETUP_TO_COMPLETE));
+  // Mimic custom passphrase being set during initial sync setup.
+  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+  ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization());
+  GetSyncService()->GetUserSettings()->SetSyncRequested(true);
+  GetSyncService()->GetUserSettings()->SetEncryptionPassphrase("hunter2");
+  GetClient(0)->FinishSyncSetup();
 
   ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
-  // If WaitForEncryptedServerBookmarks() succeeds, that means that a
-  // cryptographer initialized with only the key params was able to decrypt the
-  // data, so the data must be encrypted using a passphrase-derived key (and not
-  // e.g. a keystore key), because that cryptographer has never seen the
-  // server-side Nigori. Furthermore, if a bookmark commit has happened only
-  // once, we are certain that no bookmarks other than those we've verified to
-  // be encrypted have been committed.
+  // Ensure that only encrypted bookmarks were committed and that they are
+  // encrypted using custom passprhase.
   EXPECT_TRUE(WaitForEncryptedServerBookmarks({{title, page_url}},
                                               /*passphrase=*/"hunter2"));
   EXPECT_THAT(observer.GetCommittedEntityNames(), ElementsAre("encrypted"));
diff --git a/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc b/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
index acf839c..9cc05ce 100644
--- a/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
@@ -207,8 +207,8 @@
 // data even further.
 IN_PROC_BROWSER_TEST_F(SingleClientPasswordsSyncTestWithVerifier,
                        CommitWithCustomPassphrase) {
-  SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  GetSyncService(0)->GetUserSettings()->SetEncryptionPassphrase("hunter2");
 
   PasswordForm form = CreateTestPasswordForm(0);
   GetVerifierProfilePasswordStoreInterface()->AddLogin(form);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a73b4694..2fa82233 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -4497,8 +4497,6 @@
       "views/extensions/extensions_request_access_button.h",
       "views/extensions/extensions_request_access_button_hover_card.cc",
       "views/extensions/extensions_request_access_button_hover_card.h",
-      "views/extensions/extensions_request_access_dialog_view.cc",
-      "views/extensions/extensions_request_access_dialog_view.h",
       "views/extensions/extensions_tabbed_menu_coordinator.cc",
       "views/extensions/extensions_tabbed_menu_coordinator.h",
       "views/extensions/extensions_tabbed_menu_view.cc",
diff --git a/chrome/browser/ui/ash/clipboard_history_browsertest.cc b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
index c310efcc..1c65c80 100644
--- a/chrome/browser/ui/ash/clipboard_history_browsertest.cc
+++ b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
@@ -350,8 +350,9 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Verifies the history menu's ui interaction with the menu item selection.
-IN_PROC_BROWSER_TEST_F(ClipboardHistoryBrowserTest, VerifySelectionBehavior) {
+// Verifies the clipboard history menu response to mouse and arrow key inputs.
+IN_PROC_BROWSER_TEST_F(ClipboardHistoryBrowserTest,
+                       VerifyMouseAndArrowKeyTraversal) {
   SetClipboardText("A");
   SetClipboardText("B");
   SetClipboardText("C");
@@ -367,7 +368,7 @@
 
   // The history menu's first item should be selected as default after the menu
   // shows. Meanwhile, its delete button should not show.
-  const views::MenuItemView* first_menu_item_view =
+  const views::MenuItemView* const first_menu_item_view =
       GetMenuItemViewForIndex(/*index=*/0);
   EXPECT_TRUE(first_menu_item_view->IsSelected());
   EXPECT_FALSE(GetHistoryItemViewForIndex(/*index=*/0)
@@ -376,7 +377,7 @@
   EXPECT_EQ(gfx::Size(256, 36), first_menu_item_view->size());
 
   // Move the mouse to the second menu item.
-  const views::MenuItemView* second_menu_item_view =
+  const views::MenuItemView* const second_menu_item_view =
       GetMenuItemViewForIndex(/*index=*/1);
   EXPECT_FALSE(second_menu_item_view->IsSelected());
   GetEventGenerator()->MoveMouseTo(
@@ -391,13 +392,13 @@
                   ->GetViewByID(MenuViewID::kDeleteButtonViewID)
                   ->GetVisible());
 
-  const views::MenuItemView* third_menu_item_view =
+  // Move the selection to the third item by pressing the arrow key.
+  const views::MenuItemView* const third_menu_item_view =
       GetMenuItemViewForIndex(/*index=*/2);
   EXPECT_FALSE(third_menu_item_view->IsSelected());
   PressAndRelease(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
 
-  // Move the selection to the third item by pressing the arrow key. The third
-  // item should be selected and its delete button should not show.
+  // The third item should be selected and its delete button should not show.
   EXPECT_FALSE(second_menu_item_view->IsSelected());
   EXPECT_TRUE(third_menu_item_view->IsSelected());
   EXPECT_FALSE(GetHistoryItemViewForIndex(/*index=*/2)
@@ -405,9 +406,57 @@
                    ->GetVisible());
 }
 
-// Verifies the selection traversal via the tab key.
+// Verifies tab traversal behavior when there is only one item in clipboard
+// history.
 IN_PROC_BROWSER_TEST_F(ClipboardHistoryBrowserTest,
-                       VerifyTabSelectionTraversal) {
+                       VerifySingleItemTabTraversal) {
+  SetClipboardText("A");
+  ShowContextMenuViaAccelerator(/*wait_for_selection=*/true);
+
+  // Verify the default state right after the menu shows.
+  ASSERT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
+  ASSERT_EQ(1u, GetContextMenu()->GetMenuItemsCount());
+
+  const views::MenuItemView* const menu_item_view =
+      GetMenuItemViewForIndex(/*index=*/0);
+  const ash::ClipboardHistoryItemView* const history_item_view =
+      GetHistoryItemViewForIndex(/*index=*/0);
+
+  EXPECT_TRUE(menu_item_view->IsSelected());
+  EXPECT_TRUE(history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(history_item_view->IsDeleteButtonPseudoFocused());
+
+  // Press the Tab key. Verify that the history item's pseudo focus moves from
+  // the main button to the delete button.
+  PressAndRelease(ui::VKEY_TAB);
+  EXPECT_TRUE(menu_item_view->IsSelected());
+  EXPECT_FALSE(history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_TRUE(history_item_view->IsDeleteButtonPseudoFocused());
+
+  // Verify that the history item's delete button shows. In addition, the
+  // delete button's inkdrop highlight should fade in or be visible because the
+  // button is focused.
+  const views::View* const delete_button =
+      history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID);
+  EXPECT_TRUE(delete_button->GetVisible());
+  EXPECT_TRUE(views::InkDrop::Get(const_cast<views::View*>(delete_button))
+                  ->GetInkDrop()
+                  ->IsHighlightFadingInOrVisible());
+
+  // Press the Tab key. Verify that the history item's pseudo focus moves from
+  // the delete button back to the main button and the delete button stops being
+  // visible.
+  PressAndRelease(ui::VKEY_TAB);
+  EXPECT_TRUE(menu_item_view->IsSelected());
+  EXPECT_TRUE(history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(history_item_view->IsDeleteButtonPseudoFocused());
+  EXPECT_FALSE(delete_button->GetVisible());
+}
+
+// Verifies tab traversal behavior when there are multiple items in clipboard
+// history.
+IN_PROC_BROWSER_TEST_F(ClipboardHistoryBrowserTest,
+                       VerifyMultiItemTabTraversal) {
   SetClipboardText("A");
   SetClipboardText("B");
   ShowContextMenuViaAccelerator(/*wait_for_selection=*/true);
@@ -415,110 +464,71 @@
   // Verify the default state right after the menu shows.
   ASSERT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
   ASSERT_EQ(2u, GetContextMenu()->GetMenuItemsCount());
-  const views::MenuItemView* first_menu_item_view =
+
+  const views::MenuItemView* const first_menu_item_view =
       GetMenuItemViewForIndex(/*index=*/0);
-  ASSERT_TRUE(first_menu_item_view->IsSelected());
-  const ash::ClipboardHistoryItemView* first_history_item_view =
-      GetHistoryItemViewForIndex(/*index=*/0);
-  ASSERT_FALSE(
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-
-  // Press the tab key.
-  PressAndRelease(ui::VKEY_TAB);
-  EXPECT_TRUE(first_menu_item_view->IsSelected());
-
-  // Verify that the first menu item's delete button shows. In addition, the
-  // delete button's inkdrop highlight should fade in or be visible.
-  const views::View* const delete_button =
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID);
-  ASSERT_TRUE(delete_button->GetVisible());
-  EXPECT_TRUE(views::InkDrop::Get(const_cast<views::View*>(delete_button))
-                  ->GetInkDrop()
-                  ->IsHighlightFadingInOrVisible());
-
-  const views::MenuItemView* second_menu_item_view =
+  const views::MenuItemView* const second_menu_item_view =
       GetMenuItemViewForIndex(/*index=*/1);
-  EXPECT_FALSE(second_menu_item_view->IsSelected());
-  const ash::ClipboardHistoryItemView* second_history_item_view =
+  const ash::ClipboardHistoryItemView* const first_history_item_view =
+      GetHistoryItemViewForIndex(/*index=*/0);
+  const ash::ClipboardHistoryItemView* const second_history_item_view =
       GetHistoryItemViewForIndex(/*index=*/1);
-  EXPECT_FALSE(
-      second_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
 
-  // Press the tab key. Verify that the second menu item is selected while its
-  // delete button is hidden.
+  EXPECT_TRUE(first_menu_item_view->IsSelected());
+  EXPECT_TRUE(first_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(first_history_item_view->IsDeleteButtonPseudoFocused());
+
+  EXPECT_FALSE(second_menu_item_view->IsSelected());
+  EXPECT_FALSE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(second_history_item_view->IsDeleteButtonPseudoFocused());
+
+  // Press the Tab key. Verify that the first menu item is still selected while
+  // the history item's pseudo focus moves from the main button to the delete
+  // button.
+  PressAndRelease(ui::VKEY_TAB);
+  EXPECT_TRUE(first_menu_item_view->IsSelected());
+  EXPECT_FALSE(first_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_TRUE(first_history_item_view->IsDeleteButtonPseudoFocused());
+
+  // Press the Tab key. Verify that the second menu item is selected and its
+  // main button has pseudo focus.
   PressAndRelease(ui::VKEY_TAB);
   EXPECT_TRUE(second_menu_item_view->IsSelected());
-  EXPECT_FALSE(
-      second_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
+  EXPECT_TRUE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(second_history_item_view->IsDeleteButtonPseudoFocused());
 
-  // Press the tab key. Verify that the second item's delete button shows.
+  // Press the Tab key. Verify that the second history item's pseudo focus moves
+  // from its main button to its delete button.
   PressAndRelease(ui::VKEY_TAB);
   EXPECT_TRUE(second_menu_item_view->IsSelected());
-  EXPECT_TRUE(
-      second_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
+  EXPECT_FALSE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_TRUE(second_history_item_view->IsDeleteButtonPseudoFocused());
 
-  // Press the tab key with the shift key pressed. Verify that the second item
-  // is selected while its delete button is hidden.
+  // Press the Tab key with the Shift key pressed. Verify that the second
+  // history item's pseudo focus goes back to its main button.
   PressAndRelease(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   EXPECT_TRUE(second_menu_item_view->IsSelected());
-  EXPECT_FALSE(
-      second_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
+  EXPECT_TRUE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(second_history_item_view->IsDeleteButtonPseudoFocused());
 
-  // Press the tab key with the shift key pressed. Verify that the first item
-  // is selected while its delete button is visible.
+  // Press the Tab key with the Shift key pressed. Verify that the first menu
+  // item is selected and its delete button has pseudo focus.
   PressAndRelease(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   EXPECT_TRUE(first_menu_item_view->IsSelected());
-  EXPECT_TRUE(
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-  EXPECT_FALSE(second_menu_item_view->IsSelected());
+  EXPECT_FALSE(first_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_TRUE(first_history_item_view->IsDeleteButtonPseudoFocused());
 
-  // Press the ENTER key. Verifies that the first item is deleted. The second
-  // item is selected and its delete button should not show.
+  EXPECT_FALSE(second_menu_item_view->IsSelected());
+  EXPECT_FALSE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(second_history_item_view->IsDeleteButtonPseudoFocused());
+
+  // Press the Enter key. Verify that the first item is deleted. The second item
+  // should now be selected and its main button should have pseudo focus.
   PressAndRelease(ui::VKEY_RETURN);
   EXPECT_EQ(1u, GetContextMenu()->GetMenuItemsCount());
   EXPECT_TRUE(second_menu_item_view->IsSelected());
-  EXPECT_FALSE(
-      second_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-}
-
-// Verifies the tab traversal on the history menu with only one item.
-IN_PROC_BROWSER_TEST_F(ClipboardHistoryBrowserTest,
-                       VerifyTabTraversalOnOneItemMenu) {
-  SetClipboardText("A");
-  ShowContextMenuViaAccelerator(/*wait_for_selection=*/true);
-
-  // Verify the default state right after the menu shows.
-  ASSERT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
-  ASSERT_EQ(1u, GetContextMenu()->GetMenuItemsCount());
-  const ash::ClipboardHistoryItemView* first_history_item_view =
-      GetHistoryItemViewForIndex(/*index=*/0);
-  ASSERT_FALSE(
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-  const views::MenuItemView* first_menu_item_view =
-      GetMenuItemViewForIndex(/*index=*/0);
-  ASSERT_TRUE(first_menu_item_view->IsSelected());
-
-  // Press the tab key. Verify that the delete button is visible.
-  PressAndRelease(ui::VKEY_TAB);
-  ASSERT_TRUE(
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-
-  // Press the tab key. Verify that the delete button is hidden. The menu item
-  // is still under selection.
-  PressAndRelease(ui::VKEY_TAB);
-  ASSERT_FALSE(
-      first_history_item_view->GetViewByID(MenuViewID::kDeleteButtonViewID)
-          ->GetVisible());
-  EXPECT_TRUE(first_menu_item_view->IsSelected());
+  EXPECT_TRUE(second_history_item_view->IsMainButtonPseudoFocused());
+  EXPECT_FALSE(second_history_item_view->IsDeleteButtonPseudoFocused());
 }
 
 // Verifies that the history menu is anchored at the cursor's location when
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index c2499d8..44830334 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -94,6 +94,7 @@
   kFeedbackSourceChannelIndicator,
   kFeedbackSourceLauncher,
   kFeedbackSourceSettingsPerformancePage,
+  kFeedbackSourceQuickOffice,
 
   // Must be last.
   kFeedbackSourceCount,
diff --git a/chrome/browser/ui/color/new_tab_page_color_mixer.cc b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
index ad64a28..50c2359 100644
--- a/chrome/browser/ui/color/new_tab_page_color_mixer.cc
+++ b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
@@ -312,7 +312,7 @@
         /* 10% opacity */ 0.1 * SK_AlphaOPAQUE);
     mixer[kColorRealboxResultsDimSelected] = {
         kColorOmniboxResultsBackgroundSelected};
-    mixer[kColorRealboxResultsForeground] = {ui::kColorTextfieldForeground};
+    mixer[kColorRealboxResultsForeground] = {kColorOmniboxText};
     mixer[kColorRealboxResultsForegroundDimmed] = {
         kColorOmniboxResultsTextDimmed};
     mixer[kColorRealboxResultsIcon] = {kColorOmniboxResultsIcon};
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller.h b/chrome/browser/ui/passwords/password_generation_popup_controller.h
index cb4ef1e..df42c3b4 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller.h
@@ -40,6 +40,7 @@
   virtual GenerationUIState state() const = 0;
   virtual bool password_selected() const = 0;
   virtual const std::u16string& password() const = 0;
+  virtual bool IsUserTypedPasswordWeak() const = 0;
 
   // Translated strings
   virtual std::u16string SuggestedText() = 0;
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
index 190d4e4..dbe10279 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
@@ -226,6 +226,18 @@
     weak_this->HideImpl();
 }
 
+// TODO(crbug.com/1345766): Add test checking that delayed call to this function
+// does not hide generation popup triggered by an empty password field.
+void PasswordGenerationPopupControllerImpl::OnWeakCheckComplete(bool is_weak) {
+  user_typed_password_is_weak_ = is_weak;
+
+  if (is_weak) {
+    Show(kOfferGeneration);
+  } else if (!user_typed_password_.empty()) {
+    HideImpl();
+  }
+}
+
 void PasswordGenerationPopupControllerImpl::Show(GenerationUIState state) {
   // When switching from editing to generation state, regenerate the password.
   if (state == kOfferGeneration &&
@@ -270,6 +282,28 @@
     observer_->OnPopupShown(state_);
 }
 
+void PasswordGenerationPopupControllerImpl::
+    UpdatePopupBasedOnTypedPasswordStrength() {
+  if (user_typed_password_.empty()) {
+    user_typed_password_is_weak_ = false;
+    Show(kOfferGeneration);
+    return;
+  }
+
+#if !BUILDFLAG(IS_ANDROID)
+  if (!password_strength_calculation_) {
+    password_strength_calculation_ =
+        std::make_unique<password_manager::PasswordStrengthCalculation>();
+  }
+  password_manager::PasswordStrengthCalculation::CompletionCallback completion =
+      base::BindOnce(
+          &PasswordGenerationPopupControllerImpl::OnWeakCheckComplete,
+          weak_ptr_factory_.GetWeakPtr());
+  password_strength_calculation_->CheckPasswordWeakInSandbox(
+      base::UTF16ToUTF8(user_typed_password_), std::move(completion));
+#endif  // !BUILDFLAG(IS_ANDROID)
+}
+
 void PasswordGenerationPopupControllerImpl::UpdateTypedPassword(
     const std::u16string& new_user_typed_password) {
   user_typed_password_ = new_user_typed_password;
@@ -407,3 +441,7 @@
 const std::u16string& PasswordGenerationPopupControllerImpl::HelpText() {
   return help_text_;
 }
+
+bool PasswordGenerationPopupControllerImpl::IsUserTypedPasswordWeak() const {
+  return user_typed_password_is_weak_;
+}
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
index a382dcb..35efbf9 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
@@ -24,6 +24,7 @@
 #include "ui/gfx/native_widget_types.h"
 
 #if !BUILDFLAG(IS_ANDROID)
+#include "components/password_manager/core/browser/password_strength_calculation.h"
 #include "components/zoom/zoom_observer.h"
 #endif  // !BUILDFLAG(IS_ANDROID)
 
@@ -90,6 +91,12 @@
   // Create a PasswordGenerationPopupView if one doesn't already exist.
   void Show(GenerationUIState state);
 
+  // Updates popup based on the strength of the password typed by the user.
+  // If typed password is empty, creates a popup without the strength indicator.
+  // If typed password is weak, creates a popup with the strength indicator.
+  // If typed password is not weak, hides the popup (if one exists).
+  void UpdatePopupBasedOnTypedPasswordStrength();
+
   // Update the password typed by the user.
   void UpdateTypedPassword(const std::u16string& new_user_typed_password);
 
@@ -163,6 +170,7 @@
   const std::u16string& password() const override;
   std::u16string SuggestedText() override;
   const std::u16string& HelpText() override;
+  bool IsUserTypedPasswordWeak() const override;
 
   bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event);
 
@@ -172,6 +180,10 @@
   // Accept password if it's selected.
   bool PossiblyAcceptPassword();
 
+  // Displays password generation dropdown with strength indicator when
+  // `is_weak` is true, hides the dropdown otherwise.
+  void OnWeakCheckComplete(bool is_weak);
+
   // Handle to the popup. May be NULL if popup isn't showing.
   raw_ptr<PasswordGenerationPopupView> view_;
 
@@ -205,16 +217,25 @@
   // strength.
   std::u16string user_typed_password_;
 
+  // Whether the password currently typed by the user is weak.
+  bool user_typed_password_is_weak_ = false;
+
   // The current password that is considered generated. This is the password to
   // be displayed in the user generation dialog.
   std::u16string current_generated_password_;
 
   // Whether the row with the password is currently selected/highlighted.
-  bool password_selected_;
+  bool password_selected_ = false;
 
   // The state of the generation popup.
   GenerationUIState state_;
 
+#if !BUILDFLAG(IS_ANDROID)
+  // Calculates password strength in a sandboxed utility process.
+  std::unique_ptr<password_manager::PasswordStrengthCalculation>
+      password_strength_calculation_;
+#endif
+
   std::unique_ptr<KeyPressRegistrator> key_press_handler_manager_;
 
   base::WeakPtrFactory<PasswordGenerationPopupControllerImpl> weak_ptr_factory_{
diff --git a/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc b/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc
index 051cb79..fb4fd65e 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc
@@ -7,6 +7,8 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/password_manager/password_manager_uitest_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/passwords/password_generation_popup_controller_impl.h"
@@ -14,6 +16,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/password_manager/content/browser/content_password_manager_driver_factory.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -21,6 +24,20 @@
 
 class PasswordGenerationPopupViewTest : public InProcessBrowserTest {};
 
+class PasswordGenerationPopupViewWithStrengthIndicatorTest
+    : public InProcessBrowserTest {
+ public:
+  PasswordGenerationPopupViewWithStrengthIndicatorTest() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{password_manager::features::
+                                  kPasswordStrengthIndicator},
+        /*disabled_features=*/{});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Regression test for crbug.com/400543. Verifying that moving the mouse in the
 // editing dialog doesn't crash.
 IN_PROC_BROWSER_TEST_F(PasswordGenerationPopupViewTest,
@@ -118,4 +135,101 @@
   EXPECT_FALSE(controller);
 }
 
+IN_PROC_BROWSER_TEST_F(PasswordGenerationPopupViewWithStrengthIndicatorTest,
+                       ShowsPopupWithEmptyPasswordField) {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  password_generation::PasswordGenerationUIData ui_data(
+      gfx::RectF(web_contents->GetContainerBounds().x(),
+                 web_contents->GetContainerBounds().y(), 10, 10),
+      /*max_length=*/10,
+      /*generation_element=*/std::u16string(),
+      /*user_typed_password=*/std::u16string(), FieldRendererId(100),
+      /*is_generation_element_password_type=*/true, base::i18n::TextDirection(),
+      FormData());
+
+  base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
+      PasswordGenerationPopupControllerImpl::GetOrCreate(
+          /*previous=*/nullptr, ui_data.bounds, ui_data,
+          password_manager::ContentPasswordManagerDriverFactory::
+              FromWebContents(web_contents)
+                  ->GetDriverForFrame(web_contents->GetPrimaryMainFrame())
+                  ->AsWeakPtr(),
+          /*observer=*/nullptr, web_contents,
+          web_contents->GetPrimaryMainFrame());
+
+  EXPECT_FALSE(controller->IsVisible());
+  controller->UpdatePopupBasedOnTypedPasswordStrength();
+  EXPECT_TRUE(controller->IsVisible());
+
+  web_contents->Close();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordGenerationPopupViewWithStrengthIndicatorTest,
+                       ShowsPopupWithWeakPasswordTyped) {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  password_generation::PasswordGenerationUIData ui_data(
+      gfx::RectF(web_contents->GetContainerBounds().x(),
+                 web_contents->GetContainerBounds().y(), 10, 10),
+      /*max_length=*/10,
+      /*generation_element=*/std::u16string(),
+      /*user_typed_password=*/std::u16string(), FieldRendererId(100),
+      /*is_generation_element_password_type=*/true, base::i18n::TextDirection(),
+      FormData());
+
+  TestGenerationPopupObserver observer;
+  base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
+      PasswordGenerationPopupControllerImpl::GetOrCreate(
+          /*previous=*/nullptr, ui_data.bounds, ui_data,
+          password_manager::ContentPasswordManagerDriverFactory::
+              FromWebContents(web_contents)
+                  ->GetDriverForFrame(web_contents->GetPrimaryMainFrame())
+                  ->AsWeakPtr(),
+          &observer, web_contents, web_contents->GetPrimaryMainFrame());
+
+  EXPECT_FALSE(controller->IsVisible());
+  controller->UpdateTypedPassword(u"weak");
+  controller->UpdatePopupBasedOnTypedPasswordStrength();
+  observer.WaitForStatus(TestGenerationPopupObserver::GenerationPopup::kShown);
+  EXPECT_TRUE(controller->IsVisible());
+
+  web_contents->Close();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordGenerationPopupViewWithStrengthIndicatorTest,
+                       HidesPopupWithStrongPasswordTyped) {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  password_generation::PasswordGenerationUIData ui_data(
+      gfx::RectF(web_contents->GetContainerBounds().x(),
+                 web_contents->GetContainerBounds().y(), 10, 10),
+      /*max_length=*/10,
+      /*generation_element=*/std::u16string(),
+      /*user_typed_password=*/std::u16string(), FieldRendererId(100),
+      /*is_generation_element_password_type=*/true, base::i18n::TextDirection(),
+      FormData());
+
+  TestGenerationPopupObserver observer;
+  base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
+      PasswordGenerationPopupControllerImpl::GetOrCreate(
+          /*previous=*/nullptr, ui_data.bounds, ui_data,
+          password_manager::ContentPasswordManagerDriverFactory::
+              FromWebContents(web_contents)
+                  ->GetDriverForFrame(web_contents->GetPrimaryMainFrame())
+                  ->AsWeakPtr(),
+          &observer, web_contents, web_contents->GetPrimaryMainFrame());
+
+  // Make the popup visible first.
+  EXPECT_FALSE(controller->IsVisible());
+  controller->UpdatePopupBasedOnTypedPasswordStrength();
+  EXPECT_TRUE(controller->IsVisible());
+
+  // Popup should be hidden and controller destroyed with strong password typed.
+  controller->UpdateTypedPassword(u"fnxsr4@cm^mdls#fkbhisg3d");
+  controller->UpdatePopupBasedOnTypedPasswordStrength();
+  observer.WaitForStatus(TestGenerationPopupObserver::GenerationPopup::kHidden);
+  EXPECT_FALSE(controller);
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/side_search/side_search_tab_contents_helper_unittest.cc b/chrome/browser/ui/side_search/side_search_tab_contents_helper_unittest.cc
index 092e7892..8bca817 100644
--- a/chrome/browser/ui/side_search/side_search_tab_contents_helper_unittest.cc
+++ b/chrome/browser/ui/side_search/side_search_tab_contents_helper_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/feature_list.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/side_search/side_search_config.h"
@@ -116,6 +117,11 @@
 };
 
 TEST_F(SideSearchTabContentsHelperTest, LastSearchURLUpdatesCorrectly) {
+  // TODO(crbug.com/1384174): Update this test to pass with the
+  // kUnifiedSidePanel flag enabled.
+  if (base::FeatureList::IsEnabled(features::kUnifiedSidePanel))
+    GTEST_SKIP();
+
   // When a tab is first opened there should be no last encountered search URL.
   EXPECT_FALSE(helper()->last_search_url().has_value());
   EXPECT_TRUE(GetLastCommittedSideContentsEntry()->IsInitialEntry());
@@ -162,6 +168,11 @@
 }
 
 TEST_F(SideSearchTabContentsHelperTest, IndicatesWhenSidePanelShouldBeShown) {
+  // TODO(crbug.com/1384174): Update this test to pass with the
+  // kUnifiedSidePanel flag enabled.
+  if (base::FeatureList::IsEnabled(features::kUnifiedSidePanel))
+    GTEST_SKIP();
+
   // With no initial navigation the side panel should not be showing.
   EXPECT_FALSE(helper()->CanShowSidePanelForCommittedNavigation());
 
@@ -217,6 +228,11 @@
 }
 
 TEST_F(SideSearchTabContentsHelperTest, EmitsReturnedToSRPMetrics) {
+  // TODO(crbug.com/1384174): Update this test to pass with the
+  // kUnifiedSidePanel flag enabled.
+  if (base::FeatureList::IsEnabled(features::kUnifiedSidePanel))
+    GTEST_SKIP();
+
   // Navigating to a matching search. Then navigate to a non-matching URL and
   // navigate back, doing so twice.
   LoadURL(kSearchMatchUrl1);
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 119e55ab..16f6a85a 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -312,7 +312,7 @@
 
 BASE_FEATURE(kUnifiedSidePanel,
              "UnifiedSidePanel",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // This enables enables persistence of a WebContents in a 1-to-1 association
 // with the current Profile for WebUI bubbles. See https://crbug.com/1177048.
diff --git a/chrome/browser/ui/views/accelerator_table.cc b/chrome/browser/ui/views/accelerator_table.cc
index f6d5a034..a3ecd7f 100644
--- a/chrome/browser/ui/views/accelerator_table.cc
+++ b/chrome/browser/ui/views/accelerator_table.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <vector>
+
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
 #include "base/no_destructor.h"
@@ -237,7 +239,8 @@
     {ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, IDC_TOGGLE_QUICK_COMMANDS},
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 #endif  // !BUILDFLAG(IS_MAC)
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) && \
+    (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS))
     {ui::VKEY_S, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
      IDC_RUN_SCREEN_AI_VISUAL_ANNOTATIONS},
 #endif
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
index 0307c4a..d93b52d1 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/views/extensions/extensions_dialogs_utils.h"
 #include "chrome/browser/ui/views/extensions/extensions_request_access_button_hover_card.h"
-#include "chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "chrome/grit/generated_resources.h"
@@ -50,9 +49,6 @@
 }
 
 void ExtensionsRequestAccessButton::MaybeShowHoverCard() {
-  // TODO(crbug.com/1319555): Don't show the hover card if the dialog opened by
-  // pressing the button is still open. This will be easier to add once we
-  // address the TODO below for blocked action dialog.
   if (ExtensionsRequestAccessButtonHoverCard::IsShowing() ||
       !GetWidget()->IsMouseEventsEnabled())
     return;
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.cc b/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.cc
deleted file mode 100644
index 81071da..0000000
--- a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h"
-
-#include "base/callback_helpers.h"
-#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
-#include "chrome/browser/ui/views/extensions/extensions_dialogs_utils.h"
-#include "chrome/grit/generated_resources.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/models/dialog_model.h"
-#include "ui/views/bubble/bubble_dialog_model_host.h"
-
-namespace {
-
-std::u16string GetTitle(
-    const std::vector<ToolbarActionViewController*>& actions,
-    std::u16string current_site) {
-  if (actions.size() == 1) {
-    return l10n_util::GetStringFUTF16(
-        IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_SINGLE_EXTENSION_TITLE,
-        actions[0]->GetActionName(), current_site);
-  }
-  return l10n_util::GetStringFUTF16(
-      IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_MULTIPLE_EXTENSIONS_TITLE,
-      current_site);
-}
-
-}  // namespace
-
-void ShowExtensionsRequestAccessDialogView(
-    content::WebContents* web_contents,
-    views::View* anchor_view,
-    std::vector<ToolbarActionViewController*> actions) {
-  DCHECK(!actions.empty());
-  DCHECK(web_contents);
-
-  ui::DialogModel::Builder dialog_builder =
-      ui::DialogModel::Builder(std::make_unique<ui::DialogModelDelegate>());
-  dialog_builder
-      .SetTitle(GetTitle(actions, GetCurrentHost(web_contents)))
-      // TODO(crbug.com/1239772): Grant access to all the extensions when dialog
-      // is accepted
-      .AddOkButton(base::DoNothing(),
-                   l10n_util::GetStringUTF16(
-                       IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_OK_BUTTON_LABEL))
-      .AddCancelButton(
-          base::DoNothing(),
-          l10n_util::GetStringUTF16(
-              IDS_EXTENSIONS_REQUEST_ACCESS_BUBBLE_CANCEL_BUTTON_LABEL));
-
-  if (actions.size() == 1) {
-    dialog_builder.SetIcon(GetIcon(actions[0], web_contents));
-  } else {
-    for (auto* action : actions) {
-      dialog_builder.AddMenuItem(
-          GetIcon(action, web_contents), action->GetActionName(),
-          base::DoNothing(),
-          ui::DialogModelMenuItem::Params().SetIsEnabled(false));
-    }
-  }
-
-  auto bubble = std::make_unique<views::BubbleDialogModelHost>(
-      dialog_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT);
-  views::BubbleDialogDelegate::CreateBubble(std::move(bubble))->Show();
-}
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h b/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h
deleted file mode 100644
index bd34b03..0000000
--- a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_DIALOG_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_DIALOG_VIEW_H_
-
-#include <vector>
-
-namespace content {
-class WebContents;
-}  // namespace content
-
-namespace views {
-class View;
-}  // namespace views
-
-class ToolbarActionViewController;
-
-void ShowExtensionsRequestAccessDialogView(
-    content::WebContents* web_contents,
-    views::View* anchor_view,
-    std::vector<ToolbarActionViewController*> extensions);
-
-#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc
deleted file mode 100644
index 201df3b6..0000000
--- a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/extensions/extensions_request_access_dialog_view.h"
-
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
-#include "chrome/browser/ui/views/extensions/extensions_dialogs_browsertest.h"
-#include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
-#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
-#include "content/public/test/browser_test.h"
-#include "extensions/common/extension_features.h"
-
-class ExtensionsRequestAccessDialogViewBrowserTest
-    : public ExtensionsDialogBrowserTest {
- public:
-  // DialogBrowserTest:
-  void ShowUi(const std::string& name) override {
-    // Install extension so the extensions menu button is visible and can serve
-    // as the dialog's anchor point.
-    InstallExtension("Extension");
-    views::View* const anchor_view =
-        extensions_container()->GetExtensionsButton();
-    EXPECT_TRUE(anchor_view->GetVisible());
-
-    auto controller_A = std::make_unique<TestToolbarActionViewController>("A");
-    std::vector<ToolbarActionViewController*> extensions_requesting_access;
-    extensions_requesting_access.push_back(controller_A.get());
-
-    ShowExtensionsRequestAccessDialogView(
-        browser()->tab_strip_model()->GetActiveWebContents(), anchor_view,
-        extensions_requesting_access);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_{
-      extensions_features::kExtensionsMenuAccessControl};
-};
-
-// TODO(crbug.com/1339738): Flaky on win-clang and win/win64 trunk builds. 
-// ExtensionsRequestAccessDialog may not longer be used, wait to see if the class is
-// deleted before fixing this.
-IN_PROC_BROWSER_TEST_F(ExtensionsRequestAccessDialogViewBrowserTest, DISABLED_InvokeUi) {
-  ShowAndVerifyUi();
-}
diff --git a/chrome/browser/ui/views/frame/DEPS b/chrome/browser/ui/views/frame/DEPS
index 98d14392..a7be0a4e 100644
--- a/chrome/browser/ui/views/frame/DEPS
+++ b/chrome/browser/ui/views/frame/DEPS
@@ -10,6 +10,10 @@
   "dbus_appmenu\.*": [
     "+dbus",
   ],
+  # Parts of this class use Ash, but only when run in classic Ash mode.
+  "picture_in_picture_browser_frame_view\.*": [
+    "+ash",
+  ],
   "top_controls_slide_controller_chromeos_browsertest.cc": [
     "+cc/base/math_util.h",
   ],
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index c7ff2b5..29ca536 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -39,6 +39,14 @@
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/wm/window_util.h"
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/ui/frame/interior_resize_handler_targeter.h"
+#endif
+
 namespace {
 
 // TODO(https://crbug.com/1346734): Check whether any of the below should be
@@ -50,13 +58,12 @@
 // The height of the controls bar at the top of the window.
 constexpr int kTopControlsHeight = 30;
 
+#if BUILDFLAG(IS_LINUX)
 // Frame border when window shadow is not drawn.
 constexpr int kFrameBorderThickness = 4;
-
-#if BUILDFLAG(IS_LINUX)
-constexpr int kResizeBorder = 10;
 #endif
 
+constexpr int kResizeBorder = 10;
 constexpr int kResizeAreaCornerSize = 16;
 
 // The window has a smaller minimum size than normal Chrome windows.
@@ -164,6 +171,16 @@
 #if BUILDFLAG(IS_LINUX)
   frame_background_ = std::make_unique<views::FrameBackground>();
 #endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  ash::window_util::InstallResizeHandleWindowTargeterForWindow(
+      frame->GetNativeWindow());
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  frame->GetNativeWindow()->SetEventTargeter(
+      std::make_unique<chromeos::InteriorResizeHandleTargeter>());
+#endif
 }
 
 PictureInPictureBrowserFrameView::~PictureInPictureBrowserFrameView() = default;
@@ -222,7 +239,7 @@
 
   // Allow dragging and resizing the window.
   int window_component = GetHTComponentForFrame(
-      point, FrameBorderInsets(), kResizeAreaCornerSize, kResizeAreaCornerSize,
+      point, ResizeBorderInsets(), kResizeAreaCornerSize, kResizeAreaCornerSize,
       GetWidget()->widget_delegate()->CanResize());
   if (window_component != HTNOWHERE)
     return window_component;
@@ -608,7 +625,15 @@
       ShouldDrawFrameShadow(), gfx::Insets(kFrameBorderThickness),
       frame()->tiled_edges(), GetShadowValues(), kResizeBorder);
 #else
-  return gfx::Insets(kFrameBorderThickness);
+  return gfx::Insets();
+#endif
+}
+
+gfx::Insets PictureInPictureBrowserFrameView::ResizeBorderInsets() const {
+#if BUILDFLAG(IS_LINUX)
+  return FrameBorderInsets();
+#else
+  return gfx::Insets(kResizeBorder);
 #endif
 }
 
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index 6dcdc9c..57408db 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -147,6 +147,9 @@
   // Returns the insets of the window frame borders.
   gfx::Insets FrameBorderInsets() const;
 
+  // Returns the insets of the window frame borders for resizing.
+  gfx::Insets ResizeBorderInsets() const;
+
   // Returns the height of the top bar area, including the window top border.
   int GetTopAreaHeight() const;
 
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
index d7cce4d..2addf34 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
@@ -276,10 +276,7 @@
   const int kHorizontalMargin =
       provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_HORIZONTAL);
 
-  if (base::FeatureList::IsEnabled(
-          password_manager::features::kPasswordStrengthIndicator)) {
-    // TODO(crbug.com/1345766): Adjust according to the calculated password
-    // strength.
+  if (controller_->IsUserTypedPasswordWeak()) {
     auto* password_strength_view = AddChildView(CreatePasswordStrengthView(
         l10n_util::GetStringUTF16(IDS_PASSWORD_WEAKNESS_INDICATOR)));
     password_strength_view->SetBorder(views::CreateEmptyBorder(
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
index e720fb6c..201197c2 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
@@ -589,14 +589,6 @@
   helper_.CheckInstallIconNotShown();
 }
 
-IN_PROC_BROWSER_TEST_F(WebAppIntegration, WAI_29MinimalUiBrowser) {
-  // Test contents are generated by script. Please do not modify!
-  // See `docs/webapps/why-is-this-test-failing.md` or
-  // `docs/webapps/integration-testing-framework` for more info.
-  // Sheriffs: Disabling this test is supported.
-  helper_.CreateShortcut(Site::kMinimalUi, WindowOptions::kBrowser);
-}
-
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
     WAI_29StandaloneWindowed_24_12Standalone_7Standalone_112StandaloneNotShown_39StandaloneMinimalUi_16_27_14) {
@@ -3917,26 +3909,6 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-IN_PROC_BROWSER_TEST_F(WebAppIntegration,
-                       WAI_32MinimalUiWithShortcutBrowserWebApp) {
-  // Test contents are generated by script. Please do not modify!
-  // See `docs/webapps/why-is-this-test-failing.md` or
-  // `docs/webapps/integration-testing-framework` for more info.
-  // Sheriffs: Disabling this test is supported.
-  helper_.InstallPolicyApp(Site::kMinimalUi, ShortcutOptions::kWithShortcut,
-                           WindowOptions::kBrowser, InstallMode::kWebApp);
-}
-
-IN_PROC_BROWSER_TEST_F(WebAppIntegration,
-                       WAI_32MinimalUiNoShortcutBrowserWebApp) {
-  // Test contents are generated by script. Please do not modify!
-  // See `docs/webapps/why-is-this-test-failing.md` or
-  // `docs/webapps/integration-testing-framework` for more info.
-  // Sheriffs: Disabling this test is supported.
-  helper_.InstallPolicyApp(Site::kMinimalUi, ShortcutOptions::kNoShortcut,
-                           WindowOptions::kBrowser, InstallMode::kWebApp);
-}
-
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
     WAI_29StandaloneWindowed_24_12Standalone_7Standalone_112StandaloneNotShown_73_37Standalone_19) {
@@ -4883,5 +4855,43 @@
   helper_.CheckWindowControlsOverlay(Site::kWco, IsOn::kOn);
 }
 
+IN_PROC_BROWSER_TEST_F(
+    WebAppIntegration,
+    WAI_31Standalone_24_12Standalone_7Standalone_112StandaloneNotShown_68StandaloneAcceptUpdate_117Standalone_110StandaloneRed) {
+  // Test contents are generated by script. Please do not modify!
+  // See `docs/webapps/why-is-this-test-failing.md` or
+  // `docs/webapps/integration-testing-framework` for more info.
+  // Sheriffs: Disabling this test is supported.
+  helper_.InstallOmniboxIcon(InstallableSite::kStandalone);
+  helper_.CheckWindowCreated();
+  helper_.CheckAppInListWindowed(Site::kStandalone);
+  helper_.CheckPlatformShortcutAndIcon(Site::kStandalone);
+  helper_.CheckWindowControlsOverlayToggle(Site::kStandalone,
+                                           IsShown::kNotShown);
+  helper_.ManifestUpdateIcon(Site::kStandalone,
+                             UpdateDialogResponse::kAcceptUpdate);
+  helper_.AwaitManifestUpdate(Site::kStandalone);
+  helper_.CheckAppIcon(Site::kStandalone, Color::kRed);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    WebAppIntegration,
+    WAI_47Standalone_24_12Standalone_7Standalone_112StandaloneNotShown_68StandaloneAcceptUpdate_117Standalone_110StandaloneRed) {
+  // Test contents are generated by script. Please do not modify!
+  // See `docs/webapps/why-is-this-test-failing.md` or
+  // `docs/webapps/integration-testing-framework` for more info.
+  // Sheriffs: Disabling this test is supported.
+  helper_.InstallMenuOption(InstallableSite::kStandalone);
+  helper_.CheckWindowCreated();
+  helper_.CheckAppInListWindowed(Site::kStandalone);
+  helper_.CheckPlatformShortcutAndIcon(Site::kStandalone);
+  helper_.CheckWindowControlsOverlayToggle(Site::kStandalone,
+                                           IsShown::kNotShown);
+  helper_.ManifestUpdateIcon(Site::kStandalone,
+                             UpdateDialogResponse::kAcceptUpdate);
+  helper_.AwaitManifestUpdate(Site::kStandalone);
+  helper_.CheckAppIcon(Site::kStandalone, Color::kRed);
+}
+
 }  // namespace
 }  // namespace web_app::integration_tests
diff --git a/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
index 2011d22..00ca936 100644
--- a/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
+++ b/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
@@ -14,6 +14,7 @@
 #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
 #include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h"
 #include "chrome/browser/ash/web_applications/camera_app/chrome_camera_app_ui_delegate.h"
+#include "chrome/browser/ash/web_applications/shortcut_customization_ui/chrome_shortcut_customization_delegate.h"
 #include "chrome/browser/ui/webui/ash/account_manager/account_manager_error_ui.h"
 #include "chrome/browser/ui/webui/ash/account_manager/account_migration_welcome_ui.h"
 #include "chrome/browser/ui/webui/ash/add_supervision/add_supervision_ui.h"
@@ -90,6 +91,11 @@
   map.AddWebUIConfig(
       MakeComponentConfig<ash::CameraAppUIConfig, ash::CameraAppUI,
                           ChromeCameraAppUIDelegate>());
+  map.AddWebUIConfig(
+      MakeComponentConfig<ash::ShortcutCustomizationAppUIConfig,
+                          ash::ShortcutCustomizationAppUI,
+                          ChromeShortcutCustomizationDelegate>());
+
   map.AddWebUIConfig(std::make_unique<ash::AccountManagerErrorUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::AccountMigrationWelcomeUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::AddSupervisionUIConfig>());
@@ -133,7 +139,6 @@
   map.AddWebUIConfig(std::make_unique<ash::PasswordChangeUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::PowerUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::SetTimeUIConfig>());
-  map.AddWebUIConfig(std::make_unique<ash::ShortcutCustomizationAppUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::SlowTraceControllerConfig>());
   map.AddWebUIConfig(std::make_unique<ash::SlowUIConfig>());
   map.AddWebUIConfig(
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
index 8fa1ef2..834bab1 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
@@ -512,27 +512,23 @@
     const std::vector<history::Cluster> clusters_batch,
     bool can_load_more,
     bool is_continuation) {
+  // TODO(tommycli): It's weird that there's one more post-processing step here
+  // that's not encapsulated within `QueryClustersState`. That's because
+  // `EntityImageService` can't live in the component yet, because it depends
+  // on code in the /chrome directory. Fix this using dependency injection.
   auto* entity_image_service = EntityImageService::Get(GetProfile());
-  // Kick off a bunch of image fetch requests if this feature is enabled.
-  for (size_t i = 0; i < clusters_batch.size(); ++i) {
-    auto& cluster = clusters_batch[i];
-    if (cluster.label_source != history::Cluster::LabelSource::kSearch) {
-      continue;
-    }
+  entity_image_service->PopulateEntityImagesFor(
+      std::move(clusters_batch),
+      base::BindOnce(&HistoryClustersHandler::SendClustersToPage,
+                     weak_ptr_factory_.GetWeakPtr(), query, can_load_more,
+                     is_continuation));
+}
 
-    if (!cluster.raw_label || cluster.raw_label->empty())
-      continue;
-
-    // TODO(tommycli): Populate this with the actual entity ID once available.
-    std::string entity_id;
-    size_t cluster_index =
-        query_clusters_state_->number_clusters_sent_to_page() + i;
-    entity_image_service->FetchImageFor(
-        *cluster.raw_label, entity_id,
-        base::BindOnce(&HistoryClustersHandler::OnImageFetchedForCluster,
-                       weak_ptr_factory_.GetWeakPtr(), cluster_index));
-  }
-
+void HistoryClustersHandler::SendClustersToPage(
+    const std::string& query,
+    bool can_load_more,
+    bool is_continuation,
+    const std::vector<history::Cluster> clusters_batch) {
   auto query_result =
       QueryClustersResultToMojom(profile_, query, std::move(clusters_batch),
                                  can_load_more, is_continuation);
@@ -543,13 +539,6 @@
   LaunchJourneysSurvey();
 }
 
-void HistoryClustersHandler::OnImageFetchedForCluster(size_t cluster_index,
-                                                      const GURL& image_url) {
-  if (!image_url.is_valid())
-    return;
-  page_->OnClusterImageUpdated(cluster_index, image_url);
-}
-
 void HistoryClustersHandler::LaunchJourneysSurvey() {
   // All the below is to attempt launch a survey, after loading the first set of
   // clusters.
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
index 3290a6c..ee4088b 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
@@ -99,15 +99,18 @@
   Profile* GetProfile() override;
 
  private:
-  // Called with the result of querying clusters. Subsequently, the result is
-  // is sent to the JS to update the UI.
+  // Called with the result of querying clusters. Does one more round of
+  // post-processing, then calls `SendClustersToPage()`.
   void OnGotClustersBatch(const std::string& query,
                           const std::vector<history::Cluster> clusters_batch,
                           bool can_load_more,
                           bool is_continuation);
 
-  // Callback for images fetched for clusters.
-  void OnImageFetchedForCluster(size_t cluster_index, const GURL& image_url);
+  // Callback to `OnGotClustersBatch()`.
+  void SendClustersToPage(const std::string& query,
+                          bool can_load_more,
+                          bool is_continuation,
+                          const std::vector<history::Cluster> clusters_batch);
 
   // Launches the Journeys survey, if user is eligible.
   void LaunchJourneysSurvey();
diff --git a/chrome/browser/ui/webui/invalidations/invalidations_ui.cc b/chrome/browser/ui/webui/invalidations/invalidations_ui.cc
index 66750c2..9b61351 100644
--- a/chrome/browser/ui/webui/invalidations/invalidations_ui.cc
+++ b/chrome/browser/ui/webui/invalidations/invalidations_ui.cc
@@ -29,7 +29,9 @@
       "'unsafe-eval';");
   source->AddResourcePath("test_loader_util.js",
                           IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
-  source->DisableTrustedTypesCSP();
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::TrustedTypes,
+      "trusted-types jstemplate webui-test-script;");
   source->AddResourcePaths(
       base::make_span(kInvalidationsResources, kInvalidationsResourcesSize));
   source->SetDefaultResource(IDR_INVALIDATIONS_ABOUT_INVALIDATIONS_HTML);
diff --git a/chrome/browser/ui/webui/sync_file_system_internals/extension_statuses_handler.cc b/chrome/browser/ui/webui/sync_file_system_internals/extension_statuses_handler.cc
index 7488a35f..98d1c44 100644
--- a/chrome/browser/ui/webui/sync_file_system_internals/extension_statuses_handler.cc
+++ b/chrome/browser/ui/webui/sync_file_system_internals/extension_statuses_handler.cc
@@ -100,9 +100,9 @@
     return;
   }
 
-  sync_service->GetExtensionStatusMap(
-      base::BindOnce(&ConvertExtensionStatusToDictionary,
-                     extension_service->AsWeakPtr(), std::move(callback)));
+  sync_service->GetExtensionStatusMap(base::BindOnce(
+      &ConvertExtensionStatusToDictionary,
+      extension_service->AsExtensionServiceWeakPtr(), std::move(callback)));
 }
 
 void ExtensionStatusesHandler::HandleGetExtensionStatuses(
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index e7f72d1..2f8cb73 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1669031864-63b0b0032d6a090362866d49188e5ef44e9fe52c.profdata
+chrome-linux-main-1669052379-349c9b09fc496ab00d547f4d7a2d7b10068fd2f3.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 2e9ef9a..8f1379d7 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1669031864-d48a189ff6c71cf5e85969defaf6f9724ab2b856.profdata
+chrome-mac-arm-main-1669052379-c0d13ae4b3813f67ea71a047d260d6eb215ac467.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index de8ce4cbf..78559a8f8 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1669031864-c66f15651feb086287df0998ee34279131694d3b.profdata
+chrome-mac-main-1669052379-0db4490b297ef571c169300f9ae958f9623dea28.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a0864bd0..b273ed0 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1669031864-39e75e7ffe92a3412b931f296cb39e2d3f5451fa.profdata
+chrome-win32-main-1669042776-901bce6dc7880edbf227ab7d1793b8aabb385ce3.profdata
diff --git a/chrome/services/printing/print_backend_service_impl.cc b/chrome/services/printing/print_backend_service_impl.cc
index d8a0c00c..3201f736 100644
--- a/chrome/services/printing/print_backend_service_impl.cc
+++ b/chrome/services/printing/print_backend_service_impl.cc
@@ -241,6 +241,7 @@
   if (result != mojom::ResultCode::kSuccess) {
     DLOG(ERROR) << "Failure updating printer settings for document "
                 << document_->cookie() << ", error: " << result;
+    context_->Cancel();
     return result;
   }
 
@@ -248,6 +249,7 @@
   if (result != mojom::ResultCode::kSuccess) {
     DLOG(ERROR) << "Failure initializing new document " << document_->cookie()
                 << ", error: " << result;
+    context_->Cancel();
     return result;
   }
 
@@ -269,8 +271,12 @@
 
   absl::optional<RenderData> render_data =
       PrepareRenderData(document_->cookie(), page_data_type, serialized_page);
-  if (!render_data)
+  if (!render_data) {
+    DLOG(ERROR) << "Failure preparing render data for document "
+                << document_->cookie();
+    context_->Cancel();
     return mojom::ResultCode::kFailed;
+  }
 
   document_->SetPage(page_index, std::move(render_data->metafile),
                      shrink_factor, page_size, page_content_rect);
@@ -280,10 +286,9 @@
   if (result != mojom::ResultCode::kSuccess) {
     DLOG(ERROR) << "Failure rendering page " << page_index << " of document "
                 << document_->cookie() << ", error: " << result;
-    return result;
+    context_->Cancel();
   }
-
-  return mojom::ResultCode::kSuccess;
+  return result;
 }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -297,20 +302,36 @@
 
   absl::optional<RenderData> render_data =
       PrepareRenderData(document_->cookie(), data_type, serialized_document);
-  if (!render_data)
+  if (!render_data) {
+    DLOG(ERROR) << "Failure preparing render data for document "
+                << document_->cookie();
+    context_->Cancel();
     return mojom::ResultCode::kFailed;
+  }
 
   document_->set_page_count(page_count);
   document_->SetDocument(std::move(render_data->metafile));
 
-  return document_->RenderPrintedDocument(context_.get());
+  mojom::ResultCode result = document_->RenderPrintedDocument(context_.get());
+  if (result != mojom::ResultCode::kSuccess) {
+    DLOG(ERROR) << "Failure rendering document " << document_->cookie()
+                << ", error: " << result;
+    context_->Cancel();
+  }
+  return result;
 }
 
 mojom::ResultCode DocumentContainer::DoDocumentDone() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DVLOG(1) << "Document done for document " << document_->cookie();
-  return context_->DocumentDone();
+  mojom::ResultCode result = context_->DocumentDone();
+  if (result != mojom::ResultCode::kSuccess) {
+    DLOG(ERROR) << "Failure completing document " << document_->cookie()
+                << ", error: " << result;
+    context_->Cancel();
+  }
+  return result;
 }
 
 void DocumentContainer::DoCancel() {
@@ -738,16 +759,11 @@
   DocumentHelper* document_helper = GetDocumentHelper(document_cookie);
   DCHECK(document_helper);
 
-  // Safe to use `base::Unretained(this)` because `this` outlives the async
-  // call and callback.  The entire service process goes away when `this`
-  // lifetime expires.
   document_helper->document_container()
       .AsyncCall(&DocumentContainer::DoRenderPrintedPage)
       .WithArgs(page_index, page_data_type, std::move(serialized_page),
                 page_size, page_content_rect, shrink_factor)
-      .Then(base::BindOnce(&PrintBackendServiceImpl::OnDidRenderPrintedPage,
-                           base::Unretained(this), std::ref(*document_helper),
-                           std::move(callback)));
+      .Then(std::move(callback));
 }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -760,15 +776,10 @@
   DocumentHelper* document_helper = GetDocumentHelper(document_cookie);
   DCHECK(document_helper);
 
-  // Safe to use `base::Unretained(this)` because `this` outlives the async
-  // call and callback.  The entire service process goes away when `this`
-  // lifetime expires.
   document_helper->document_container()
       .AsyncCall(&DocumentContainer::DoRenderPrintedDocument)
       .WithArgs(page_count, data_type, std::move(serialized_document))
-      .Then(base::BindOnce(&PrintBackendServiceImpl::OnDidRenderPrintedDocument,
-                           base::Unretained(this), std::ref(*document_helper),
-                           std::move(callback)));
+      .Then(std::move(callback));
 }
 
 void PrintBackendServiceImpl::DocumentDone(
@@ -809,37 +820,6 @@
     mojom::ResultCode result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
   document_helper.TakeStartPrintingCallback().Run(result);
-  if (result == mojom::ResultCode::kSuccess)
-    return;
-
-  // Remove this document due to the failure to do setup.
-  RemoveDocumentHelper(document_helper);
-}
-
-#if BUILDFLAG(IS_WIN)
-void PrintBackendServiceImpl::OnDidRenderPrintedPage(
-    DocumentHelper& document_helper,
-    mojom::PrintBackendService::RenderPrintedPageCallback callback,
-    mojom::ResultCode result) {
-  std::move(callback).Run(result);
-  if (result == mojom::ResultCode::kSuccess)
-    return;
-
-  // Remove this document due to the rendering failure.
-  RemoveDocumentHelper(document_helper);
-}
-#endif  // BUILDFLAG(IS_WIN)
-
-void PrintBackendServiceImpl::OnDidRenderPrintedDocument(
-    DocumentHelper& document_helper,
-    mojom::PrintBackendService::RenderPrintedDocumentCallback callback,
-    mojom::ResultCode result) {
-  std::move(callback).Run(result);
-  if (result == mojom::ResultCode::kSuccess)
-    return;
-
-  // Remove this document due to the rendering failure.
-  RemoveDocumentHelper(document_helper);
 }
 
 void PrintBackendServiceImpl::OnDidDocumentDone(
@@ -848,8 +828,12 @@
     mojom::ResultCode result) {
   std::move(callback).Run(result);
 
-  // All complete for this document.
-  RemoveDocumentHelper(document_helper);
+  // The service expects that the calling process will call `Cancel()` if there
+  // are any errors during printing.
+  if (result == mojom::ResultCode::kSuccess) {
+    // All complete for this document.
+    RemoveDocumentHelper(document_helper);
+  }
 }
 
 void PrintBackendServiceImpl::OnDidCancel(
diff --git a/chrome/services/printing/print_backend_service_impl.h b/chrome/services/printing/print_backend_service_impl.h
index bdbc1dc4..6658512 100644
--- a/chrome/services/printing/print_backend_service_impl.h
+++ b/chrome/services/printing/print_backend_service_impl.h
@@ -203,16 +203,6 @@
   // Callbacks from worker functions.
   void OnDidStartPrintingReadyDocument(DocumentHelper& document_helper,
                                        mojom::ResultCode result);
-#if BUILDFLAG(IS_WIN)
-  void OnDidRenderPrintedPage(
-      DocumentHelper& document_helper,
-      mojom::PrintBackendService::RenderPrintedPageCallback callback,
-      mojom::ResultCode result);
-#endif
-  void OnDidRenderPrintedDocument(
-      DocumentHelper& document_helper,
-      mojom::PrintBackendService::RenderPrintedDocumentCallback callback,
-      mojom::ResultCode result);
   void OnDidDocumentDone(
       DocumentHelper& document_helper,
       mojom::PrintBackendService::DocumentDoneCallback callback,
diff --git a/chrome/services/sharing/webrtc/ipc_network_manager.cc b/chrome/services/sharing/webrtc/ipc_network_manager.cc
index 59c497b..4775596 100644
--- a/chrome/services/sharing/webrtc/ipc_network_manager.cc
+++ b/chrome/services/sharing/webrtc/ipc_network_manager.cc
@@ -106,8 +106,8 @@
     if (adapter_type == rtc::ADAPTER_TYPE_UNKNOWN) {
       adapter_type = rtc::GetAdapterTypeFromName(it->name.c_str());
     }
-    auto network = std::make_unique<rtc::Network>(
-        it->name, it->name, prefix, it->prefix_length, adapter_type);
+    auto network = CreateNetwork(it->name, it->name, prefix, it->prefix_length,
+                                 adapter_type);
     network->set_default_local_address_provider(this);
     network->set_mdns_responder_provider(this);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index dcb1ee91..15192f17 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3396,7 +3396,6 @@
         "../browser/ui/views/extensions/extensions_dialogs_browsertest.cc",
         "../browser/ui/views/extensions/extensions_dialogs_browsertest.h",
         "../browser/ui/views/extensions/extensions_request_access_button_hover_card_browsertest.cc",
-        "../browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc",
         "../browser/ui/views/extensions/media_galleries_dialog_views_browsertest.cc",
         "../browser/ui/views/extensions/reload_page_dialog_browsertest.cc",
         "../browser/ui/views/extensions/settings_overridden_dialog_browsertest.cc",
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 5abfba4..638afcd 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -5036,7 +5036,8 @@
               '--remote-debugging-port=%d' % port,
               '--user-data-dir=%s' % temp_dir,
               '--use-mock-keychain',
-              '--password-store=basic']
+              '--password-store=basic',
+              'about:blank']
         process = subprocess.Popen(cmd)
         try:
           driver = self.CreateDriver(
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.ts b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.ts
index 2bd6bf1..191315f7 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.ts
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.ts
@@ -9,7 +9,7 @@
 import {AcceleratorLookupManager} from 'chrome://shortcut-customization/js/accelerator_lookup_manager.js';
 import {AcceleratorSubsectionElement} from 'chrome://shortcut-customization/js/accelerator_subsection.js';
 import {fakeAcceleratorConfig, fakeLayoutInfo} from 'chrome://shortcut-customization/js/fake_data.js';
-import {AcceleratorSource, Modifier} from 'chrome://shortcut-customization/js/shortcut_types.js';
+import {AcceleratorSource, LayoutInfo, LayoutStyle, Modifier} from 'chrome://shortcut-customization/js/shortcut_types.js';
 import {assertEquals} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
@@ -50,16 +50,22 @@
         /*key=*/ 67,
         /*keyDisplay=*/ 'c');
 
-    const accelerators = [acceleratorInfo1, acceleratorInfo2];
+    const expectedAccelInfos = [acceleratorInfo1, acceleratorInfo2];
     const description = 'test shortcut';
     const title = 'test title';
+    const expectedLayoutInfo: LayoutInfo = {
+      action: 0,
+      category: 0,
+      description,
+      source: AcceleratorSource.kAsh,
+      style: LayoutStyle.kDefault,
+      subCategory: 0,
+    };
 
     sectionElement!.title = title;
-    sectionElement!.acceleratorContainer = [{
-      description: description,
-      acceleratorInfos: accelerators,
-      source: AcceleratorSource.kAsh,
-      action: 0,
+    sectionElement!.accelRowDataArray = [{
+      acceleratorInfos: expectedAccelInfos,
+      layoutInfo: expectedLayoutInfo,
     }];
 
     await flush();
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
index 002362712..40e0322 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.ts
@@ -112,7 +112,7 @@
     // Asert 2 accelerators are loaded for Window Management.
     assertEquals(
         (expectedLayouts!.get(windowManagementValue) as LayoutInfo[]).length,
-        subSections[0]!.acceleratorContainer!.length);
+        subSections[0]!.accelRowDataArray!.length);
 
     // Assert subsection title (Virtual Desks) matches expected value from
     // fake lookup.
@@ -122,7 +122,7 @@
     // Asert 2 accelerators are loaded for Virtual Desks.
     assertEquals(
         (expectedLayouts!.get(virtualDesksValue) as LayoutInfo[]).length,
-        subSections[1]!.acceleratorContainer!.length);
+        subSections[1]!.accelRowDataArray!.length);
   });
 
   test('LoadFakeBrowserPage', async () => {
@@ -154,7 +154,7 @@
     // Assert only 1 accelerator is within Tabs.
     assertEquals(
         (expectedLayouts!.get(keyIterator.value) as LayoutInfo[]).length,
-        subSections[0]!.acceleratorContainer.length);
+        subSections[0]!.accelRowDataArray.length);
   });
 
   test('OpenDialogFromAccelerator', async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js b/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js
index 705644e6..cf3ee697 100644
--- a/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js
@@ -553,7 +553,6 @@
       aboutPageEndOfLifeMessage: 'message',
     });
     await initNewPage();
-    page.scroller = page.offsetParent;
     assertTrue(!!page.$['detailed-build-info-trigger']);
     page.$['detailed-build-info-trigger'].click();
     const buildInfoPage =
@@ -581,7 +580,6 @@
       aboutPageEndOfLifeMessage: '',
     });
     await initNewPage();
-    page.scroller = page.offsetParent;
     assertTrue(!!page.$['detailed-build-info-trigger']);
     page.$['detailed-build-info-trigger'].click();
     const buildInfoPage =
@@ -595,14 +593,12 @@
       aboutPageEndOfLifeMessage: 'message',
     });
     await initNewPage();
-    page.scroller = page.offsetParent;
     assertTrue(!!page.$['detailed-build-info-trigger']);
     page.$['detailed-build-info-trigger'].click();
     checkEndOfLifeSection();
   });
 
   function getBuildInfoPage() {
-    page.scroller = page.offsetParent;
     assertTrue(!!page.$['detailed-build-info-trigger']);
     page.$['detailed-build-info-trigger'].click();
     const buildInfoPage =
diff --git a/chrome/test/data/webui/settings/settings_main_test.ts b/chrome/test/data/webui/settings/settings_main_test.ts
index 4540858..9cfc3b9 100644
--- a/chrome/test/data/webui/settings/settings_main_test.ts
+++ b/chrome/test/data/webui/settings/settings_main_test.ts
@@ -290,4 +290,13 @@
         loadTimeData.getStringF(
             'settingsAltPageTitle', loadTimeData.getString('aboutPageTitle')));
   });
+
+  test('uses parent title for navigable dialog routes', function() {
+    Router.getInstance().navigateTo(routes.CLEAR_BROWSER_DATA);
+    assertEquals(
+        document.title,
+        loadTimeData.getStringF(
+            'settingsAltPageTitle',
+            loadTimeData.getString('privacyPageTitle')));
+  });
 });
diff --git a/chrome/test/data/webui/side_panel/bookmarks/power_bookmarks_list_test.ts b/chrome/test/data/webui/side_panel/bookmarks/power_bookmarks_list_test.ts
index 31c153e..397b22a 100644
--- a/chrome/test/data/webui/side_panel/bookmarks/power_bookmarks_list_test.ts
+++ b/chrome/test/data/webui/side_panel/bookmarks/power_bookmarks_list_test.ts
@@ -7,9 +7,11 @@
 
 import {BookmarksApiProxyImpl} from 'chrome://read-later.top-chrome/bookmarks/bookmarks_api_proxy.js';
 import {ShoppingListApiProxyImpl} from 'chrome://read-later.top-chrome/bookmarks/commerce/shopping_list_api_proxy.js';
+import {PowerBookmarkRowElement} from 'chrome://read-later.top-chrome/bookmarks/power_bookmark_row.js';
 import {PowerBookmarksListElement} from 'chrome://read-later.top-chrome/bookmarks/power_bookmarks_list.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
-import {assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {TestPluralStringProxy} from 'chrome://webui-test/test_plural_string_proxy.js';
 
@@ -21,33 +23,40 @@
   let bookmarksApi: TestBookmarksApiProxy;
   let shoppingListApi: TestShoppingListApiProxy;
 
-  const topLevelBookmarks: chrome.bookmarks.BookmarkTreeNode[] = [
+  const folders: chrome.bookmarks.BookmarkTreeNode[] = [
     {
       id: '1',
       parentId: '0',
-      title: 'First child bookmark',
-      url: 'http://child/bookmark/1/',
-      dateAdded: 1,
-    },
-    {
-      id: '2',
-      parentId: '0',
-      title: 'Second child bookmark',
-      url: 'http://child/bookmark/2/',
-      dateAdded: 3,
-    },
-    {
-      id: '3',
-      parentId: '0',
-      title: 'Child folder',
-      dateAdded: 2,
+      title: 'Bookmarks bar',
       children: [
         {
-          id: '5',
-          parentId: '3',
-          title: 'Nested bookmark',
-          url: 'http://nested/bookmark/',
-          dateAdded: 4,
+          id: '2',
+          parentId: '1',
+          title: 'First child bookmark',
+          url: 'http://child/bookmark/1/',
+          dateAdded: 1,
+        },
+        {
+          id: '3',
+          parentId: '1',
+          title: 'Second child bookmark',
+          url: 'http://child/bookmark/2/',
+          dateAdded: 3,
+        },
+        {
+          id: '4',
+          parentId: '1',
+          title: 'Child folder',
+          dateAdded: 2,
+          children: [
+            {
+              id: '5',
+              parentId: '4',
+              title: 'Nested bookmark',
+              url: 'http://nested/bookmark/',
+              dateAdded: 4,
+            },
+          ],
         },
       ],
     },
@@ -61,8 +70,7 @@
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
 
     bookmarksApi = new TestBookmarksApiProxy();
-    bookmarksApi.setTopLevelBookmarks(
-        JSON.parse(JSON.stringify(topLevelBookmarks)));
+    bookmarksApi.setFolders(JSON.parse(JSON.stringify(folders)));
     BookmarksApiProxyImpl.setInstance(bookmarksApi);
 
     shoppingListApi = new TestShoppingListApiProxy();
@@ -78,20 +86,121 @@
   });
 
   test('GetsAndShowsTopLevelBookmarks', () => {
-    assertEquals(1, bookmarksApi.getCallCount('getTopLevelBookmarks'));
+    assertEquals(1, bookmarksApi.getCallCount('getFolders'));
     assertEquals(
-        topLevelBookmarks.length,
+        folders[0]!.children!.length,
         getBookmarkElements(powerBookmarksList).length);
   });
 
   test('DefaultsToSortByNewest', () => {
-    assertEquals(1, bookmarksApi.getCallCount('getTopLevelBookmarks'));
     const bookmarkElements = getBookmarkElements(powerBookmarksList);
     // All folders should come first
-    assertEquals(bookmarkElements[0]!.id, 'bookmark-3');
+    assertEquals(bookmarkElements[0]!.id, 'bookmark-4');
     // Newest URL should come next
-    assertEquals(bookmarkElements[1]!.id, 'bookmark-2');
+    assertEquals(bookmarkElements[1]!.id, 'bookmark-3');
     // Older URL should be last
-    assertEquals(bookmarkElements[2]!.id, 'bookmark-1');
+    assertEquals(bookmarkElements[2]!.id, 'bookmark-2');
+  });
+
+  test('UpdatesChangedBookmarks', () => {
+    const changedBookmark = folders[0]!.children![0]!;
+    bookmarksApi.callbackRouter.onChanged.callListeners(changedBookmark.id, {
+      title: 'New title',
+      url: 'http://new/url',
+    });
+
+    const bookmarkElement = getBookmarkElements(powerBookmarksList)[2]!;
+    assertEquals(
+        'New title',
+        (bookmarkElement as PowerBookmarkRowElement).bookmark.title);
+    assertEquals(
+        'http://new/url',
+        (bookmarkElement as PowerBookmarkRowElement).bookmark.url);
+    assertNotEquals(
+        undefined,
+        Array.from(bookmarkElement.shadowRoot!.querySelectorAll('div'))
+            .find(el => el.textContent === 'New title'));
+  });
+
+  test('AddsCreatedBookmark', async () => {
+    bookmarksApi.callbackRouter.onCreated.callListeners('999', {
+      id: '999',
+      title: 'New bookmark',
+      index: 0,
+      parentId: folders[0]!.id,
+      url: 'http://new/bookmark',
+    });
+    flush();
+
+    const bookmarkElements = getBookmarkElements(powerBookmarksList);
+    assertEquals(4, bookmarkElements.length);
+  });
+
+  test('AddsCreatedBookmarkForNewFolder', () => {
+    // Create a new folder without a children array.
+    bookmarksApi.callbackRouter.onCreated.callListeners('1000', {
+      id: '1000',
+      title: 'New folder',
+      index: 0,
+      parentId: folders[0]!.id,
+    });
+    flush();
+
+    // Create a new bookmark within that folder.
+    bookmarksApi.callbackRouter.onCreated.callListeners('1001', {
+      id: '1001',
+      title: 'New bookmark in new folder',
+      index: 0,
+      parentId: '1000',
+      url: 'http://google.com',
+    });
+    flush();
+
+    const newFolder = getBookmarkElements(powerBookmarksList)[0]!;
+    assertEquals(
+        1, (newFolder as PowerBookmarkRowElement).bookmark.children!.length);
+  });
+
+  test('MovesBookmarks', () => {
+    const movedBookmark = folders[0]!.children![2]!.children![0]!;
+    bookmarksApi.callbackRouter.onMoved.callListeners(movedBookmark.id, {
+      index: 0,
+      parentId: folders[0]!.id,                   // Moving to bookmarks bar.
+      oldParentId: folders[0]!.children![2]!.id,  // Moving from child folder.
+      oldIndex: 0,
+    });
+    flush();
+
+    const bookmarkElements = getBookmarkElements(powerBookmarksList);
+    assertEquals(4, bookmarkElements.length);
+    const childFolder = bookmarkElements[0]!;
+    assertEquals('4', (childFolder as PowerBookmarkRowElement).bookmark.id);
+    assertEquals(
+        0, (childFolder as PowerBookmarkRowElement).bookmark.children!.length);
+  });
+
+  test('MovesBookmarksIntoNewFolder', () => {
+    // Create a new folder without a children array.
+    bookmarksApi.callbackRouter.onCreated.callListeners('1000', {
+      id: '1000',
+      title: 'New folder',
+      index: 0,
+      parentId: folders[0]!.id,
+    });
+    flush();
+
+    const movedBookmark = folders[0]!.children![2]!.children![0]!;
+    bookmarksApi.callbackRouter.onMoved.callListeners(movedBookmark.id, {
+      index: 0,
+      parentId: '1000',
+      oldParentId: folders[0]!.children![2]!.id,
+      oldIndex: 0,
+    });
+    flush();
+
+    const newFolder =
+        powerBookmarksList.shadowRoot!.querySelector('#bookmark-1000');
+    assertEquals(
+        1, (newFolder as PowerBookmarkRowElement).bookmark.children!.length);
   });
 });
diff --git a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
index bc4087c..22460e9 100644
--- a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
+++ b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
@@ -10,7 +10,6 @@
 
 export class TestBookmarksApiProxy extends TestBrowserProxy implements
     BookmarksApiProxy {
-  private topLevelBookmarks_: chrome.bookmarks.BookmarkTreeNode[] = [];
   private folders_: chrome.bookmarks.BookmarkTreeNode[] = [];
   callbackRouter: {
     onChanged: FakeChromeEvent,
@@ -22,7 +21,6 @@
 
   constructor() {
     super([
-      'getTopLevelBookmarks',
       'getFolders',
       'bookmarkCurrentTab',
       'openBookmark',
@@ -42,11 +40,6 @@
     };
   }
 
-  getTopLevelBookmarks() {
-    this.methodCalled('getTopLevelBookmarks');
-    return Promise.resolve(this.topLevelBookmarks_);
-  }
-
   getFolders() {
     this.methodCalled('getFolders');
     return Promise.resolve(this.folders_);
@@ -62,10 +55,6 @@
     this.methodCalled('openBookmark', id, depth, clickModifiers, source);
   }
 
-  setTopLevelBookmarks(topLevelBookmarks: chrome.bookmarks.BookmarkTreeNode[]) {
-    this.topLevelBookmarks_ = topLevelBookmarks;
-  }
-
   setFolders(folders: chrome.bookmarks.BookmarkTreeNode[]) {
     this.folders_ = folders;
   }
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn b/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
index 980129f..04f1be5d 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
+++ b/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
@@ -20,6 +20,10 @@
   in_files = [
     "shortcuts_test.ts",
     "test_support.ts",
+    "app_test.ts",
+    "appearance_test.ts",
+    "categories_test.ts",
+    "themes_test.ts",
   ]
   definitions = [ "//tools/typescript/definitions/metrics_private.d.ts" ]
   deps = [
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
new file mode 100644
index 0000000..d4061dd
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://webui-test/mojo_webui_test_support.js';
+import 'chrome://customize-chrome-side-panel.top-chrome/app.js';
+
+import {AppElement} from 'chrome://customize-chrome-side-panel.top-chrome/app.js';
+import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+suite('AppTest', () => {
+  let customizeChromeApp: AppElement;
+
+  setup(async () => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    customizeChromeApp = document.createElement('customize-chrome-app');
+    document.body.appendChild(customizeChromeApp);
+  });
+
+  test('app changes pages', async () => {
+    // Test initial page state.
+    assertTrue(
+        customizeChromeApp.$.overviewPage.classList.contains('iron-selected'));
+
+    // Send event for edit theme being clicked.
+    customizeChromeApp.$.appearanceElement.dispatchEvent(
+        new Event('edit-theme-click'));
+    // Current page should now be categories.
+    assertTrue(customizeChromeApp.$.categoriesPage.classList.contains(
+        'iron-selected'));
+
+    // Send event for category selected.
+    customizeChromeApp.$.categoriesPage.dispatchEvent(
+        new CustomEvent<object>('category-select', {detail: {}}));
+    // Current page should now be themes.
+    assertTrue(
+        customizeChromeApp.$.themesPage.classList.contains('iron-selected'));
+
+    // Send event for theme selected.
+    customizeChromeApp.$.themesPage.dispatchEvent(new Event('theme-select'));
+    // Current page should now be overview.
+    assertTrue(
+        customizeChromeApp.$.overviewPage.classList.contains('iron-selected'));
+
+    // Set page back to themes and then go back a page.
+    customizeChromeApp.$.categoriesPage.dispatchEvent(
+        new CustomEvent<object>('category-select', {detail: {}}));
+    customizeChromeApp.$.themesPage.dispatchEvent(new Event('back-click'));
+    // Current page should now be categories.
+    assertTrue(customizeChromeApp.$.categoriesPage.classList.contains(
+        'iron-selected'));
+    // Go back again.
+    customizeChromeApp.$.categoriesPage.dispatchEvent(new Event('back-click'));
+    // Current page should now be overview.
+    assertTrue(
+        customizeChromeApp.$.overviewPage.classList.contains('iron-selected'));
+  });
+});
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/appearance_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/appearance_test.ts
new file mode 100644
index 0000000..6eff157
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/customize_chrome/appearance_test.ts
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://webui-test/mojo_webui_test_support.js';
+import 'chrome://customize-chrome-side-panel.top-chrome/appearance.js';
+
+import {AppearanceElement} from 'chrome://customize-chrome-side-panel.top-chrome/appearance.js';
+import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+suite('AppearanceTest', () => {
+  let appearanceElement: AppearanceElement;
+
+  setup(async () => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    appearanceElement = document.createElement('customize-chrome-appearance');
+    document.body.appendChild(appearanceElement);
+  });
+
+  test('appearance edit button creates event', async () => {
+    const eventPromise = eventToPromise('edit-theme-click', appearanceElement);
+    appearanceElement.$.editThemeButton.click();
+    const event = await eventPromise;
+    assertTrue(!!event);
+  });
+});
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
new file mode 100644
index 0000000..9a27af5f2
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://webui-test/mojo_webui_test_support.js';
+import 'chrome://customize-chrome-side-panel.top-chrome/categories.js';
+
+import {CategoriesElement} from 'chrome://customize-chrome-side-panel.top-chrome/categories.js';
+import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+suite('CategoriesTest', () => {
+  let categoriesElement: CategoriesElement;
+
+  setup(async () => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    categoriesElement = document.createElement('customize-chrome-categories');
+    document.body.appendChild(categoriesElement);
+  });
+
+  test('categories buttons create events', async () => {
+    // Check that clicking the back button produces a back-click event.
+    let eventPromise = eventToPromise('back-click', categoriesElement);
+    categoriesElement.$.backButton.click();
+    let event = await eventPromise;
+    assertTrue(!!event);
+
+    // Check that clicking a category produces a category-select event.
+    eventPromise = eventToPromise('category-select', categoriesElement);
+    const category = categoriesElement.shadowRoot!.querySelector(
+                         '.category')! as HTMLButtonElement;
+    category.click();
+    event = await eventPromise;
+    assertTrue(!!event);
+  });
+});
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js b/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
index 0426e34..ad0ab7e 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
+++ b/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
@@ -26,7 +26,8 @@
   };
 }
 
-var ShortcutsTest = class extends SidePanelCustomizeChromeBrowserTest {
+var SidePanelCustomizeChromeShortcutsTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
   /** @override */
   get browsePreload() {
     return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
@@ -34,6 +35,58 @@
   }
 };
 
-TEST_F('ShortcutsTest', 'All', function() {
+var SidePanelCustomizeChromeAppTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
+        '?module=side_panel_customize_chrome/app_test.js';
+  }
+};
+
+var SidePanelCustomizeChromeAppearanceTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
+        '?module=side_panel_customize_chrome/appearance_test.js';
+  }
+};
+
+var SidePanelCustomizeChromeCategoriesTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
+        '?module=side_panel_customize_chrome/categories_test.js';
+  }
+};
+
+var SidePanelCustomizeChromeThemesTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
+        '?module=side_panel_customize_chrome/themes_test.js';
+  }
+};
+
+TEST_F('SidePanelCustomizeChromeShortcutsTest', 'All', function() {
   mocha.run();
 });
+
+TEST_F('SidePanelCustomizeChromeAppTest', 'All', function() {
+  mocha.run();
+});
+
+TEST_F('SidePanelCustomizeChromeAppearanceTest', 'All', function() {
+  mocha.run();
+});
+
+TEST_F('SidePanelCustomizeChromeCategoriesTest', 'All', function() {
+  mocha.run();
+});
+
+TEST_F('SidePanelCustomizeChromeThemesTest', 'All', function() {
+  mocha.run();
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
new file mode 100644
index 0000000..d6b8a8ce
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://webui-test/mojo_webui_test_support.js';
+import 'chrome://customize-chrome-side-panel.top-chrome/themes.js';
+
+import {ThemesElement} from 'chrome://customize-chrome-side-panel.top-chrome/themes.js';
+import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+suite('ThemesTest', () => {
+  let themesElement: ThemesElement;
+
+  setup(async () => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    themesElement = document.createElement('customize-chrome-themes');
+    document.body.appendChild(themesElement);
+  });
+
+  test('themes buttons create events', async () => {
+    // Check that clicking the back button produces a back-click event.
+    let eventPromise = eventToPromise('back-click', themesElement);
+    themesElement.$.backButton.click();
+    let event = await eventPromise;
+    assertTrue(!!event);
+
+    // Check that clicking a theme produces a theme-select event.
+    eventPromise = eventToPromise('theme-select', themesElement);
+    const theme =
+        themesElement.shadowRoot!.querySelector('.theme')! as HTMLButtonElement;
+    theme.click();
+    event = await eventPromise;
+    assertTrue(!!event);
+  });
+});
diff --git a/chrome/test/webapps/coverage/coverage_cros.tsv b/chrome/test/webapps/coverage/coverage_cros.tsv
index 4bd5da34..2cf2bc9 100644
--- a/chrome/test/webapps/coverage/coverage_cros.tsv
+++ b/chrome/test/webapps/coverage/coverage_cros.tsv
@@ -1,5 +1,5 @@
 # This is a generated file.
-# Full coverage: 50%, with partial coverage: 70%
+# Full coverage: 53%, with partial coverage: 75%
 create_shortcut_Standalone_Windowed🌕	launch_from_menu_option_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_launch_icon_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_chrome_apps_Standalone🌓	check_app_title_Standalone_StandaloneOriginal🌑
@@ -27,12 +27,8 @@
 create_shortcut_Standalone_Windowed🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_omnibox_icon_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_menu_option_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
+install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
+install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_menu_option_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_launch_icon_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_chrome_apps_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
@@ -955,183 +951,153 @@
 install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
 install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
 install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
diff --git a/chrome/test/webapps/coverage/coverage_linux.tsv b/chrome/test/webapps/coverage/coverage_linux.tsv
index 4bddd70..564e7282 100644
--- a/chrome/test/webapps/coverage/coverage_linux.tsv
+++ b/chrome/test/webapps/coverage/coverage_linux.tsv
@@ -1,5 +1,5 @@
 # This is a generated file.
-# Full coverage: 59%, with partial coverage: 81%
+# Full coverage: 62%, with partial coverage: 85%
 create_shortcut_Standalone_Windowed🌕	launch_from_menu_option_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_launch_icon_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_chrome_apps_Standalone🌓	check_app_title_Standalone_StandaloneOriginal🌑
@@ -27,12 +27,8 @@
 create_shortcut_Standalone_Windowed🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_omnibox_icon_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_menu_option_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
+install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
+install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_menu_option_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_launch_icon_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_chrome_apps_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
@@ -1172,183 +1168,153 @@
 install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
diff --git a/chrome/test/webapps/coverage/coverage_mac.tsv b/chrome/test/webapps/coverage/coverage_mac.tsv
index bc62982..b38e486 100644
--- a/chrome/test/webapps/coverage/coverage_mac.tsv
+++ b/chrome/test/webapps/coverage/coverage_mac.tsv
@@ -1,5 +1,5 @@
 # This is a generated file.
-# Full coverage: 54%, with partial coverage: 73%
+# Full coverage: 57%, with partial coverage: 77%
 create_shortcut_Standalone_Windowed🌕	launch_from_menu_option_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_launch_icon_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_chrome_apps_Standalone🌓	check_app_title_Standalone_StandaloneOriginal🌑
@@ -27,12 +27,8 @@
 create_shortcut_Standalone_Windowed🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_omnibox_icon_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_menu_option_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
+install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
+install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_menu_option_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_launch_icon_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_chrome_apps_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
@@ -1175,183 +1171,153 @@
 install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
diff --git a/chrome/test/webapps/coverage/coverage_win.tsv b/chrome/test/webapps/coverage/coverage_win.tsv
index 7e7ca2c..eb76fe2 100644
--- a/chrome/test/webapps/coverage/coverage_win.tsv
+++ b/chrome/test/webapps/coverage/coverage_win.tsv
@@ -1,5 +1,5 @@
 # This is a generated file.
-# Full coverage: 60%, with partial coverage: 82%
+# Full coverage: 63%, with partial coverage: 87%
 create_shortcut_Standalone_Windowed🌕	launch_from_menu_option_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_launch_icon_Standalone🌕	check_app_title_Standalone_StandaloneOriginal🌑
 create_shortcut_Standalone_Windowed🌕	launch_from_chrome_apps_Standalone🌓	check_app_title_Standalone_StandaloneOriginal🌑
@@ -27,12 +27,8 @@
 create_shortcut_Standalone_Windowed🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_omnibox_icon_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
 install_menu_option_Standalone🌕	manifest_update_title_Standalone_StandaloneUpdated_CancelDialogAndUninstall🌑	check_app_not_in_list_Standalone🌑	check_platform_shortcut_not_exists_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_app_in_list_icon_correct_Standalone🌑
-create_shortcut_Standalone_Windowed🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
-install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌑	await_manifest_update_Standalone🌑	check_platform_shortcut_and_icon_Standalone🌑
+install_omnibox_icon_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
+install_menu_option_Standalone🌕	manifest_update_icon_Standalone_AcceptUpdate🌕	await_manifest_update_Standalone🌕	check_app_icon_Standalone_Red🌕
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_menu_option_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_launch_icon_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
 install_policy_app_Standalone_NoShortcut_Windowed_WebApp🌓	manifest_update_title_Standalone_StandaloneUpdated_SkipUpdate🌑	check_update_dialog_not_shown🌑	await_manifest_update_Standalone🌑	launch_from_chrome_apps_Standalone🌑	check_app_title_Standalone_StandaloneUpdated🌑
@@ -1172,183 +1168,153 @@
 install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
 install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	check_site_handles_file_FileHandler_Foo🌕	check_site_handles_file_FileHandler_Bar🌕
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneFooFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleFooFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_MultipleFooFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-install_menu_option_MinimalUi🌕	launch_file_OneBarFile🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_One🌑	check_files_loaded_in_site_MinimalUi_OneBarFile🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-install_menu_option_MinimalUi🌕	launch_file_MultipleBarFiles🌑	file_handling_dialog_Allow_AskAgain🌑	check_pwa_window_created_MinimalUi_Two🌑	check_files_loaded_in_site_MinimalUi_MultipleBarFiles🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_Remember🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Allow_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_MinimalUi_Foo🌑	check_site_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_AskAgain🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Windowed🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Browser🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_omnibox_icon_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-install_menu_option_MinimalUi🌕	launch_file_OneFooFile🌑	file_handling_dialog_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_MinimalUi_Foo🌑	check_site_not_handles_file_MinimalUi_Bar🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_NotShown🌑	check_pwa_window_created_MinimalUi_One🌑
-create_shortcut_MinimalUi_Windowed🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-create_shortcut_MinimalUi_Browser🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_omnibox_icon_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_policy_app_MinimalUi_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
-install_menu_option_MinimalUi🌕	add_file_handling_policy_approval_MinimalUi🌑	remove_file_handling_policy_approval_MinimalUi🌑	launch_file_OneFooFile🌑	check_file_handling_dialog_Shown🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneFooFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleFooFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_MultipleFooFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneBarFile_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_One🌑	check_files_loaded_in_site_FileHandler_OneBarFile🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_MultipleBarFiles_Allow_AskAgain🌑	check_pwa_window_created_FileHandler_Two🌑	check_files_loaded_in_site_FileHandler_MultipleBarFiles🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_Remember🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	check_window_not_created🌑	check_site_handles_file_FileHandler_Foo🌑	check_site_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_AskAgain🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Windowed🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Browser🌕	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	launch_file_expect_dialog_FileHandler_OneFooFile_Deny_Remember🌑	check_window_not_created🌑	check_site_not_handles_file_FileHandler_Foo🌑	check_site_not_handles_file_FileHandler_Bar🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	launch_file_expect_no_dialog_FileHandler_OneFooFile🌑	check_pwa_window_created_FileHandler_One🌑
+create_shortcut_FileHandler_Windowed🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+create_shortcut_FileHandler_Browser🌕	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Windowed_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_WithShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
+install_policy_app_FileHandler_NoShortcut_Browser_WebApp🌓	add_file_handling_policy_approval_FileHandler🌑	remove_file_handling_policy_approval_FileHandler🌑	launch_file_expect_dialog_FileHandler_OneFooFile_Allow_AskAgain🌑
diff --git a/chrome/test/webapps/data/framework_supported_actions.csv b/chrome/test/webapps/data/framework_supported_actions.csv
index b5ee007..f35e27e9 100644
--- a/chrome/test/webapps/data/framework_supported_actions.csv
+++ b/chrome/test/webapps/data/framework_supported_actions.csv
@@ -1,4 +1,5 @@
 # Action base name,                                   Mac,Win,Lin,Cros
+check_app_icon,                                        🌕, 🌕,  🌕,   🌕,
 check_app_in_list_icon_correct,                        🌕, 🌕,  🌕,   🌕,
 check_app_in_list_not_locally_installed,               🌓, 🌓,  🌓,   🌓,
 check_app_in_list_tabbed,                              🌓, 🌓,  🌓,   🌓,
@@ -40,6 +41,7 @@
 launch_from_launch_icon,                               🌕, 🌕,  🌕,   🌕,
 launch_from_menu_option,                               🌕, 🌕,  🌕,   🌕,
 launch_from_platform_shortcut,                         🌓, 🌓,  🌓,   🌑,
+manifest_update_icon,                                  🌕, 🌕,  🌕,   🌕,
 manifest_update_display,                               🌕, 🌕,  🌕,   🌕,
 manifest_update_scope_to,                              🌕, 🌕,  🌕,   🌕,
 navigate_browser,                                      🌕, 🌕,  🌕,   🌕,
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 370ee2a..7c23541e 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -25,6 +25,7 @@
 #include "base/path_service.h"
 #include "base/process/launch.h"
 #include "base/process/process.h"
+#include "base/process/process_iterator.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
@@ -540,6 +541,19 @@
   return result;
 }
 
+void PrintProcesses() {
+  const std::string demarcation(72, '=');
+  VLOG(0) << "Found processes:";
+  VLOG(0) << demarcation;
+  base::ProcessIterator process_iterator(nullptr);
+  const base::ProcessIterator::ProcessEntries& process_entries =
+      process_iterator.Snapshot();
+  for (const base::ProcessEntry& entry : process_entries) {
+    VLOG(0) << entry.exe_file();
+  }
+  VLOG(0) << demarcation;
+}
+
 }  // namespace
 
 base::FilePath GetSetupExecutablePath() {
@@ -570,6 +584,8 @@
 }
 
 void Clean(UpdaterScope scope) {
+  VLOG(0) << __func__;
+
   CleanProcesses();
 
   const HKEY root = UpdaterScopeToHKeyRoot(scope);
@@ -620,12 +636,12 @@
 
   absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
-  if (path)
-    EXPECT_TRUE(base::DeletePathRecursively(*path));
-  path = GetDataDirPath(scope);
-  EXPECT_TRUE(path);
-  if (path)
-    EXPECT_TRUE(base::DeletePathRecursively(*path));
+  if (path) {
+    EXPECT_TRUE(base::DeletePathRecursively(*path)) << [&path]() {
+      PrintProcesses();
+      return path->value();
+    }();
+  }
 
   const absl::optional<base::FilePath> target_path =
       GetGoogleUpdateExePath(scope);
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index de2f265..70fe46e 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-109-5391.0-1668445159-benchmark-109.0.5414.13-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-109-5412.2-1669029745-benchmark-109.0.5414.14-r1-redacted.afdo.xz
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 768efa136..76afbc9 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -634,6 +634,11 @@
     // TODO(crbug.com/1154080): By calling this with true, autocomplete section
     // attributes will be ignored.
     form->IdentifySections(/*ignore_autocomplete=*/true);
+    // Metrics are intentionally only emitted after the sever response, not when
+    // determining heuristic types. This is done to reduce noise in the metrics,
+    // since generally only this sectioning result is used.
+    LogSectioningMetrics(form->form_signature(), form->fields_,
+                         form_interactions_ukm_logger);
   }
 
   AutofillMetrics::ServerQueryMetric metric;
diff --git a/components/autofill/core/browser/form_structure_sectioning_util.cc b/components/autofill/core/browser/form_structure_sectioning_util.cc
index e0e14c8..9dbbb7d 100644
--- a/components/autofill/core/browser/form_structure_sectioning_util.cc
+++ b/components/autofill/core/browser/form_structure_sectioning_util.cc
@@ -6,6 +6,7 @@
 
 #include <iterator>
 #include <memory>
+#include <sstream>
 #include <utility>
 
 #include "base/ranges/algorithm.h"
@@ -205,4 +206,34 @@
   }
 }
 
+void LogSectioningMetrics(
+    FormSignature form_signature,
+    base::span<const std::unique_ptr<AutofillField>> fields,
+    AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+  // UMA:
+  base::flat_map<Section, size_t> fields_per_section;
+  for (auto& field : fields)
+    ++fields_per_section[field->section];
+  AutofillMetrics::LogSectioningMetrics(fields_per_section);
+  // UKM:
+  if (form_interactions_ukm_logger) {
+    form_interactions_ukm_logger->LogSectioningHash(
+        form_signature, ComputeSectioningSignature(fields));
+  }
+}
+
+uint32_t ComputeSectioningSignature(
+    base::span<const std::unique_ptr<AutofillField>> fields) {
+  // Compute a signature by converting the fields' sections into integers and
+  // concatenating them. Finally, hash the result.
+  std::stringstream signature;
+  base::flat_map<Section, size_t> section_ids;
+  for (auto& field : fields) {
+    size_t section_id =
+        section_ids.emplace(field->section, section_ids.size()).first->second;
+    signature << section_id;
+  }
+  return StrToHash32Bit(signature.str());
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_structure_sectioning_util.h b/components/autofill/core/browser/form_structure_sectioning_util.h
index ad77236..b7235632 100644
--- a/components/autofill/core/browser/form_structure_sectioning_util.h
+++ b/components/autofill/core/browser/form_structure_sectioning_util.h
@@ -5,10 +5,13 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_STRUCTURE_SECTIONING_UTIL_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_STRUCTURE_SECTIONING_UTIL_H_
 
+#include <stdint.h>
 #include <memory>
 
 #include "base/containers/span.h"
 #include "components/autofill/core/browser/autofill_field.h"
+#include "components/autofill/core/browser/metrics/autofill_metrics.h"
+#include "components/autofill/core/common/signatures.h"
 
 namespace autofill {
 
@@ -111,6 +114,19 @@
 //   ------------------------------------------------------+-------------------
 void AssignSections(base::span<const std::unique_ptr<AutofillField>> fields);
 
+// Logs UMA and UKM metrics about the `fields`' sections.
+// UKM metrics are only logged if `form_interactions_ukm_logger` is available.
+void LogSectioningMetrics(
+    FormSignature form_signature,
+    base::span<const std::unique_ptr<AutofillField>> fields,
+    AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger);
+
+// Computes a 32-bit signature of the `fields` sections.
+// This is useful for logging Ukm metrics to detect on which sites different
+// sectioning algorithms produce different results.
+uint32_t ComputeSectioningSignature(
+    base::span<const std::unique_ptr<AutofillField>> fields);
+
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_STRUCTURE_SECTIONING_UTIL_H_
diff --git a/components/autofill/core/browser/form_structure_sectioning_util_unittest.cc b/components/autofill/core/browser/form_structure_sectioning_util_unittest.cc
index 5d8eff52..6926af3 100644
--- a/components/autofill/core/browser/form_structure_sectioning_util_unittest.cc
+++ b/components/autofill/core/browser/form_structure_sectioning_util_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "autofill_test_utils.h"
 #include "base/check_op.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/autofill/core/browser/autofill_field.h"
 #include "components/autofill/core/browser/autofill_type.h"
@@ -18,6 +19,7 @@
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
+#include "components/autofill/core/common/signatures.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -31,6 +33,14 @@
 
 namespace {
 
+using base::Bucket;
+using base::BucketsAre;
+
+constexpr char kNumberOfSectionsHistogram[] =
+    "Autofill.Sectioning.NumberOfSections";
+constexpr char kFieldsPerSectionHistogram[] =
+    "Autofill.Sectioning.FieldsPerSection";
+
 // The key information from which we build the `FormFieldData` objects for a
 // unittest.
 struct FieldTemplate {
@@ -73,6 +83,17 @@
 }
 
 class FormStructureSectioningTest : public testing::Test {
+ public:
+  void AssignSectionsAndLogMetrics(
+      const std::vector<std::unique_ptr<AutofillField>>& fields) {
+    AssignSections(fields);
+    // Since only the UMA metrics are tested, the form signature and UKM logger
+    // are irrelevant.
+    LogSectioningMetrics(FormSignature(0UL), fields,
+                         /*form_interactions_ukm_logger=*/nullptr);
+  }
+
+ private:
   test::AutofillEnvironment autofill_environment_;
 };
 
@@ -101,7 +122,8 @@
       features::kAutofillUseParameterizedSectioning, feature_parameters);
 
   auto fields = CreateExampleFields();
-  AssignSections(fields);
+  base::HistogramTester histogram_tester;
+  AssignSectionsAndLogMetrics(fields);
 
   // The evaluation order of the `Section::FromFieldIdentifier()` expressions
   // does not matter, as all `FormFieldData::host_frame` are identical.
@@ -118,6 +140,10 @@
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids)));
+  EXPECT_EQ(ComputeSectioningSignature(fields), StrToHash32Bit("001022332"));
+  histogram_tester.ExpectUniqueSample(kNumberOfSectionsHistogram, 4, 1);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kFieldsPerSectionHistogram),
+              BucketsAre(Bucket(1, 1), Bucket(2, 1), Bucket(3, 2)));
 }
 
 TEST_F(FormStructureSectioningTest,
@@ -132,7 +158,8 @@
       features::kAutofillUseParameterizedSectioning, feature_parameters);
 
   auto fields = CreateExampleFields();
-  AssignSections(fields);
+  base::HistogramTester histogram_tester;
+  AssignSectionsAndLogMetrics(fields);
 
   // The evaluation order of the `Section::FromFieldIdentifier()` expressions
   // does not matter, as all `FormFieldData::host_frame` are identical.
@@ -148,6 +175,10 @@
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids)));
+  EXPECT_EQ(ComputeSectioningSignature(fields), StrToHash32Bit("001122332"));
+  histogram_tester.ExpectUniqueSample(kNumberOfSectionsHistogram, 4, 1);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kFieldsPerSectionHistogram),
+              BucketsAre(Bucket(2, 3), Bucket(3, 1)));
 }
 
 TEST_F(FormStructureSectioningTest, ExampleFormSectioningModeCreateGaps) {
@@ -161,7 +192,8 @@
       features::kAutofillUseParameterizedSectioning, feature_parameters);
 
   auto fields = CreateExampleFields();
-  AssignSections(fields);
+  base::HistogramTester histogram_tester;
+  AssignSectionsAndLogMetrics(fields);
 
   // The evaluation order of the `Section::FromFieldIdentifier()` expressions
   // does not matter, as all `FormFieldData::host_frame` are identical.
@@ -178,6 +210,10 @@
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[6], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids)));
+  EXPECT_EQ(ComputeSectioningSignature(fields), StrToHash32Bit("001233443"));
+  histogram_tester.ExpectUniqueSample(kNumberOfSectionsHistogram, 5, 1);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kFieldsPerSectionHistogram),
+              BucketsAre(Bucket(1, 2), Bucket(2, 2), Bucket(3, 1)));
 }
 
 TEST_F(FormStructureSectioningTest, ExampleFormSectioningModeExpand) {
@@ -191,7 +227,8 @@
       features::kAutofillUseParameterizedSectioning, feature_parameters);
 
   auto fields = CreateExampleFields();
-  AssignSections(fields);
+  base::HistogramTester histogram_tester;
+  AssignSectionsAndLogMetrics(fields);
 
   // The evaluation order of the `Section::FromFieldIdentifier()` expressions
   // does not matter, as all `FormFieldData::host_frame` are identical.
@@ -208,6 +245,10 @@
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids),
                   Section::FromFieldIdentifier(*fields[4], frame_token_ids)));
+  EXPECT_EQ(ComputeSectioningSignature(fields), StrToHash32Bit("001022222"));
+  histogram_tester.ExpectUniqueSample(kNumberOfSectionsHistogram, 3, 1);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kFieldsPerSectionHistogram),
+              BucketsAre(Bucket(1, 1), Bucket(3, 1), Bucket(5, 1)));
 }
 
 }  // namespace
diff --git a/components/autofill/core/browser/metrics/autofill_metrics.cc b/components/autofill/core/browser/metrics/autofill_metrics.cc
index cda1f0981..96e22d80 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics.cc
@@ -2033,6 +2033,18 @@
                               filling_stats.Total());
 }
 
+void AutofillMetrics::LogSectioningMetrics(
+    const base::flat_map<Section, size_t>& fields_per_section) {
+  constexpr base::StringPiece kBaseHistogramName = "Autofill.Sectioning.";
+  UMA_HISTOGRAM_COUNTS_100(
+      base::StrCat({kBaseHistogramName, "NumberOfSections"}),
+      fields_per_section.size());
+  for (auto& [_, section_size] : fields_per_section) {
+    UMA_HISTOGRAM_COUNTS_100(
+        base::StrCat({kBaseHistogramName, "FieldsPerSection"}), section_size);
+  }
+}
+
 // static
 void AutofillMetrics::LogServerResponseHasDataForForm(bool has_data) {
   UMA_HISTOGRAM_BOOLEAN("Autofill.ServerResponseHasDataForForm", has_data);
@@ -2724,6 +2736,15 @@
       .Record(ukm_recorder_);
 }
 
+void AutofillMetrics::FormInteractionsUkmLogger::LogSectioningHash(
+    FormSignature form_signature,
+    uint32_t sectioning_signature) {
+  ukm::builders::Autofill_Sectioning(source_id_)
+      .SetFormSignature(HashFormSignature(form_signature))
+      .SetSectioningSignature(sectioning_signature % 1024)
+      .Record(ukm_recorder_);
+}
+
 int64_t AutofillMetrics::FormTypesToBitVector(
     const DenseSet<FormType>& form_types) {
   int64_t form_type_bv = 0;
diff --git a/components/autofill/core/browser/metrics/autofill_metrics.h b/components/autofill/core/browser/metrics/autofill_metrics.h
index d67abaf7..23cd862 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics.h
+++ b/components/autofill/core/browser/metrics/autofill_metrics.h
@@ -12,6 +12,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
@@ -1015,6 +1016,14 @@
         const AutofillField& field,
         ServerFieldType old_type);
 
+    // Logs a hash of the `sectioning_signature` for a specific
+    // `form_signature`. This is useful for detecting sites where different
+    // sectioning algorithms yield different results. Emitted every time
+    // sectioning is performed and only when
+    // `AutofillUseParameterizedSectioning` is enabled.
+    void LogSectioningHash(FormSignature form_signature,
+                           uint32_t sectioning_signature);
+
    private:
     bool CanLog() const;
     int64_t MillisecondsSinceFormParsed(
@@ -1417,6 +1426,10 @@
   static void LogFieldFillingStats(FormType form_type,
                                    const FormGroupFillingStats& filling_stats);
 
+  // Logs the number of sections and the number of fields/section.
+  static void LogSectioningMetrics(
+      const base::flat_map<Section, size_t>& fields_per_section);
+
   // This should be called each time a server response is parsed for a form.
   static void LogServerResponseHasDataForForm(bool has_data);
 
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index 96e38ed..fd99b68 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -52,6 +52,7 @@
     "java/res/color/switch_thumb_tint_list.xml",
     "java/res/color/switch_track_tint.xml",
     "java/res/color/switch_track_tint_incognito_baseline_list.xml",
+    "java/res/color/text_button_ripple_color_list.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/text_button_ripple_color_list.xml b/components/browser_ui/styles/android/java/res/color/text_button_ripple_color_list.xml
new file mode 100644
index 0000000..9360730a
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/color/text_button_ripple_color_list.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+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/default_pressed_alpha"
+        android:color="@macro/default_control_color_active" android:state_pressed="true"/>
+    <item android:alpha="@dimen/default_focused_hovered_alpha"
+        android:color="@macro/default_control_color_active" android:state_focused="true" android:state_hovered="true"/>
+    <item android:alpha="@dimen/default_focused_alpha"
+        android:color="@macro/default_control_color_active" android:state_focused="true"/>
+    <item android:alpha="@dimen/default_hovered_alpha"
+        android:color="@macro/default_control_color_active" android:state_hovered="true"/>
+    <item android:color="@android:color/transparent" />
+</selector>
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 9b6207e..8491bc2 100644
--- a/components/browser_ui/styles/android/java/res/values/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values/themes.xml
@@ -72,6 +72,7 @@
         <item name="globalFilledButtonBgColor">@color/filled_button_bg_dynamic_list</item>
         <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_list</item>
         <item name="globalTextButtonTextColor">@color/default_text_color_accent1_tint_list</item>
+        <item name="globalTextButtonRippleColor">@color/text_button_ripple_color_list</item>
         <item name="globalOutlinedButtonBorderColor">@macro/divider_line_bg_color</item>
         <item name="globalLinkTextColor">?attr/colorPrimary</item>
         <item name="globalClickableSpanColor">?attr/colorPrimary</item>
diff --git a/components/browser_ui/theme/android/java/res/values/themes.xml b/components/browser_ui/theme/android/java/res/values/themes.xml
index 5e3b31a..182d1189 100644
--- a/components/browser_ui/theme/android/java/res/values/themes.xml
+++ b/components/browser_ui/theme/android/java/res/values/themes.xml
@@ -52,6 +52,7 @@
         <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
         <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
         <item name="globalTextButtonTextColor">@color/blue_when_enabled_list</item>
+        <item name="globalTextButtonRippleColor">@color/text_button_ripple_color_list_baseline</item>
         <item name="globalOutlinedButtonBorderColor">@color/divider_line_bg_color_baseline</item>
         <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
         <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
@@ -141,6 +142,7 @@
         <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
         <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
         <item name="globalTextButtonTextColor">@color/blue_when_enabled_list</item>
+        <item name="globalTextButtonRippleColor">@color/text_button_ripple_color_list_baseline</item>
         <item name="globalOutlinedButtonBorderColor">@color/divider_line_bg_color_baseline</item>
         <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
         <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
index 964c3060..3a2308bd 100644
--- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
+++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -2850,19 +2850,6 @@
                                 1);
 }
 
-TEST_F(FeedApiTest, FeedIsAblated) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  base::FieldTrialParams params;
-  scoped_feature_list.InitAndEnableFeature(kIsAblated);
-
-  base::HistogramTester histograms;
-  CreateStream();
-  histograms.ExpectUniqueSample("ContentSuggestions.Feed.UserSettingsOnStart",
-                                UserSettingsOnStart::kFeedNotEnabled, 1);
-
-  ASSERT_FALSE(network_.query_request_sent.has_value());
-}
-
 TEST_F(FeedApiTest, ReportUserSettingsFromMetadataWaaOnDpOff) {
   // Fetch a feed, so that there's stored data.
   {
diff --git a/components/feed/core/v2/public/feed_service.cc b/components/feed/core/v2/public/feed_service.cc
index 1e09774..f654a4af 100644
--- a/components/feed/core/v2/public/feed_service.cc
+++ b/components/feed/core/v2/public/feed_service.cc
@@ -296,8 +296,7 @@
 
 // static
 bool FeedService::IsEnabled(const PrefService& pref_service) {
-  return !base::FeatureList::IsEnabled(kIsAblated) &&
-         pref_service.GetBoolean(feed::prefs::kEnableSnippets);
+  return pref_service.GetBoolean(feed::prefs::kEnableSnippets);
 }
 
 // static
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index e21ccd9..5c8d75a 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -149,8 +149,6 @@
              "ShareCrowButton",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kIsAblated, "FeedAblation", base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kFeedCloseRefresh,
              "FeedCloseRefresh",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 6ed2df2d..a5e59de 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -124,9 +124,6 @@
 // component, since it is being used in the feed component.
 BASE_DECLARE_FEATURE(kShareCrowButton);
 
-// Feature that when enabled completely removes all Feeds from chrome.
-BASE_DECLARE_FEATURE(kIsAblated);
-
 // When enabled, schedule a background refresh for a feed sometime after the
 // last user engagement with that feed.
 BASE_DECLARE_FEATURE(kFeedCloseRefresh);
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index a7999a5..831d640b 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -23,6 +23,8 @@
     "content_annotations_cluster_processor.h",
     "content_visibility_cluster_finalizer.cc",
     "content_visibility_cluster_finalizer.h",
+    "context_clusterer_history_service_observer.cc",
+    "context_clusterer_history_service_observer.h",
     "features.cc",
     "features.h",
     "file_clustering_backend.cc",
@@ -100,6 +102,7 @@
     "config_unittest.cc",
     "content_annotations_cluster_processor_unittest.cc",
     "content_visibility_cluster_finalizer_unittest.cc",
+    "context_clusterer_history_service_observer_unittest.cc",
     "file_clustering_backend_unittest.cc",
     "full_membership_cluster_processor_unittest.cc",
     "history_clusters_db_tasks_unittest.cc",
diff --git a/components/history_clusters/core/DEPS b/components/history_clusters/core/DEPS
index d6aec55..b727921 100644
--- a/components/history_clusters/core/DEPS
+++ b/components/history_clusters/core/DEPS
@@ -5,6 +5,7 @@
   "+components/pref_registry",
   "+components/prefs",
   "+components/query_parser",
+  "+components/search_engines",
   "+components/site_engagement/core",
   "+components/strings/grit/components_strings.h",
   "+components/url_formatter",
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index 3b35882..99105f6 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -371,6 +371,15 @@
     DCHECK_GE(search_results_page_ranking_weight, 0.0f);
   }
 
+  // The `kHistoryClustersNavigationContextClustering` feature and child params.
+  {
+    context_clustering_clean_up_duration =
+        base::Minutes(GetFieldTrialParamByFeatureAsInt(
+            internal::kHistoryClustersNavigationContextClustering,
+            "clean_up_duration_minutes",
+            context_clustering_clean_up_duration.InMinutes()));
+  }
+
   // Lonely features without child params.
   {
     non_user_visible_debug =
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 04de34b..e08f2de 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -338,6 +338,9 @@
   // visits within a cluster. Will always be greater than or equal to 0.
   float search_results_page_ranking_weight = 2.0;
 
+  // The `kHistoryClustersNavigationContextClustering` feature and child params.
+  base::TimeDelta context_clustering_clean_up_duration = base::Minutes(10);
+
   // Lonely features without child params.
 
   // Enables debug info in non-user-visible surfaces, like Chrome Inspector.
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.cc b/components/history_clusters/core/context_clusterer_history_service_observer.cc
new file mode 100644
index 0000000..0b8eba7a
--- /dev/null
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.cc
@@ -0,0 +1,250 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history_clusters/core/context_clusterer_history_service_observer.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/time/default_clock.h"
+#include "components/history/core/browser/history_service.h"
+#include "components/history_clusters/core/config.h"
+#include "components/optimization_guide/core/new_optimization_guide_decider.h"
+#include "components/search_engines/template_url_service.h"
+
+namespace history_clusters {
+
+namespace {
+
+// Returns whether `visit` should be added to `cluster`.
+bool ShouldAddVisitToCluster(const history::VisitRow& new_visit,
+                             const std::u16string& search_terms,
+                             const InProgressCluster& in_progress_cluster) {
+  if ((new_visit.visit_time - in_progress_cluster.last_visit_time) >
+      GetConfig().cluster_navigation_time_cutoff) {
+    return false;
+  }
+
+  if (!search_terms.empty()) {
+    return search_terms == in_progress_cluster.search_terms;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+InProgressCluster::InProgressCluster() = default;
+InProgressCluster::~InProgressCluster() = default;
+InProgressCluster::InProgressCluster(const InProgressCluster&) = default;
+
+ContextClustererHistoryServiceObserver::ContextClustererHistoryServiceObserver(
+    history::HistoryService* history_service,
+    TemplateURLService* template_url_service,
+    optimization_guide::NewOptimizationGuideDecider* optimization_guide_decider)
+    : template_url_service_(template_url_service),
+      optimization_guide_decider_(optimization_guide_decider),
+      clock_(base::DefaultClock::GetInstance()) {
+  if (history_service) {
+    // History service is only null in tests.
+    history_service_observation_.Observe(history_service);
+  }
+
+  if (optimization_guide_decider_) {
+    optimization_guide_decider_->RegisterOptimizationTypes(
+        {optimization_guide::proto::HISTORY_CLUSTERS});
+  }
+  clean_up_clusters_repeating_timer_.Start(
+      FROM_HERE, GetConfig().context_clustering_clean_up_duration, this,
+      &ContextClustererHistoryServiceObserver::CleanUpClusters);
+}
+ContextClustererHistoryServiceObserver::
+    ~ContextClustererHistoryServiceObserver() = default;
+
+void ContextClustererHistoryServiceObserver::OnURLVisited(
+    history::HistoryService* history_service,
+    const history::URLRow& url_row,
+    const history::VisitRow& new_visit) {
+  if (new_visit.is_known_to_sync) {
+    // Skip synced visits.
+    //
+    // Although local visits that have been synced can have this bit flipped,
+    // local visits do not automatically get sent to sync when they just get
+    // created.
+    return;
+  }
+
+  if (optimization_guide_decider_ &&
+      optimization_guide_decider_->CanApplyOptimization(
+          url_row.url(), optimization_guide::proto::HISTORY_CLUSTERS,
+          /*optimization_metadata=*/nullptr) !=
+          optimization_guide::OptimizationGuideDecision::kTrue) {
+    // Skip visits that are on the blocklist.
+    return;
+  }
+
+  // Update the normalized URL if it's a search URL.
+  std::string normalized_url = url_row.url().possibly_invalid_spec();
+  std::u16string search_terms;
+  if (template_url_service_) {
+    absl::optional<TemplateURLService::SearchMetadata> search_metadata =
+        template_url_service_->ExtractSearchMetadata(url_row.url());
+    if (search_metadata) {
+      normalized_url = search_metadata->normalized_url.possibly_invalid_spec();
+      search_terms = search_metadata->search_terms;
+    }
+  }
+
+  // See what cluster we should add it to.
+  absl::optional<int64_t> cluster_idx;
+
+  std::vector<history::VisitID> previous_visit_ids_to_check;
+  if (new_visit.opener_visit != 0) {
+    previous_visit_ids_to_check.push_back(new_visit.opener_visit);
+  }
+  if (new_visit.referring_visit != 0) {
+    previous_visit_ids_to_check.push_back(new_visit.referring_visit);
+  }
+  if (!previous_visit_ids_to_check.empty()) {
+    // See if we have clustered any of the previous visits with opener taking
+    // precedence.
+    for (history::VisitID previous_visit_id : previous_visit_ids_to_check) {
+      auto it = visit_id_to_cluster_map_.find(previous_visit_id);
+      if (it != visit_id_to_cluster_map_.end()) {
+        cluster_idx = it->second;
+        break;
+      }
+    }
+  } else {
+    // See if we have clustered the URL. (forward-back, reload, etc.)
+    auto it = visit_url_to_cluster_map_.find(normalized_url);
+    if (it != visit_url_to_cluster_map_.end()) {
+      cluster_idx = it->second;
+    }
+  }
+
+  // See if we should add to cluster.
+  if (cluster_idx) {
+    auto& in_progress_cluster = in_progress_clusters_.at(*cluster_idx);
+    if (!ShouldAddVisitToCluster(new_visit, search_terms,
+                                 in_progress_cluster)) {
+      FinalizeCluster(*cluster_idx);
+
+      cluster_idx = absl::nullopt;
+    }
+  }
+
+  // Add a new cluster if we haven't assigned one already.
+  if (!cluster_idx) {
+    cluster_idx_counter_++;
+    cluster_idx = cluster_idx_counter_;
+
+    in_progress_clusters_.emplace(*cluster_idx, InProgressCluster());
+  }
+
+  // Add to cluster maps.
+  auto& in_progress_cluster = in_progress_clusters_.at(*cluster_idx);
+  in_progress_cluster.last_visit_time = new_visit.visit_time;
+  in_progress_cluster.visit_urls.insert(normalized_url);
+  in_progress_cluster.visit_ids.emplace_back(new_visit.visit_id);
+  in_progress_cluster.search_terms = search_terms;
+  visit_id_to_cluster_map_[new_visit.visit_id] = *cluster_idx;
+  visit_url_to_cluster_map_[normalized_url] = *cluster_idx;
+
+  // TODO(b/259466296): Persist visit.
+}
+
+void ContextClustererHistoryServiceObserver::OnURLsDeleted(
+    history::HistoryService* history_service,
+    const history::DeletionInfo& deletion_info) {
+  // Clear out everything if the user deleted all history.
+  if (deletion_info.IsAllHistory()) {
+    in_progress_clusters_.clear();
+    visit_url_to_cluster_map_.clear();
+    visit_id_to_cluster_map_.clear();
+    return;
+  }
+
+  // Delete relevant visits from in-progress clusters.
+  base::flat_set<int64_t> clusters_to_finalize;
+  for (const auto& deleted_url : deletion_info.deleted_rows()) {
+    std::string normalized_deleted_url =
+        deleted_url.url().possibly_invalid_spec();
+    if (template_url_service_) {
+      absl::optional<TemplateURLService::SearchMetadata> search_metadata =
+          template_url_service_->ExtractSearchMetadata(deleted_url.url());
+      if (search_metadata) {
+        normalized_deleted_url =
+            search_metadata->normalized_url.possibly_invalid_spec();
+      }
+    }
+    auto it = visit_url_to_cluster_map_.find(normalized_deleted_url);
+    if (it != visit_url_to_cluster_map_.end()) {
+      // TODO(b/259466296): Maybe check time range.
+      clusters_to_finalize.insert(it->second);
+    }
+  }
+
+  // Finalize clusters.
+  for (int64_t cluster_idx : clusters_to_finalize) {
+    FinalizeCluster(cluster_idx);
+  }
+}
+
+void ContextClustererHistoryServiceObserver::CleanUpClusters() {
+  if (in_progress_clusters_.empty()) {
+    // Nothing to clean up, just return.
+    return;
+  }
+
+  base::UmaHistogramCounts1000(
+      "History.Clusters.ContextClusterer.NumClusters.AtCleanUp",
+      in_progress_clusters_.size());
+
+  // See which clusters we need to clean up.
+  base::flat_set<int64_t> clusters_to_finalize;
+  for (const auto& cluster_id_and_cluster : in_progress_clusters_) {
+    if ((clock_->Now() - cluster_id_and_cluster.second.last_visit_time) >
+        GetConfig().cluster_navigation_time_cutoff) {
+      clusters_to_finalize.insert(cluster_id_and_cluster.first);
+    }
+  }
+
+  // Finalize clusters.
+  for (int64_t cluster_idx : clusters_to_finalize) {
+    FinalizeCluster(cluster_idx);
+  }
+
+  base::UmaHistogramCounts1000(
+      "History.Clusters.ContextClusterer.NumClusters.CleanedUp",
+      clusters_to_finalize.size());
+
+  base::UmaHistogramCounts1000(
+      "History.Clusters.ContextClusterer.NumClusters.PostCleanUp",
+      in_progress_clusters_.size());
+}
+
+void ContextClustererHistoryServiceObserver::FinalizeCluster(
+    int64_t cluster_idx) {
+  DCHECK(in_progress_clusters_.find(cluster_idx) !=
+         in_progress_clusters_.end());
+
+  // Delete relevant visits from in-progress maps.
+  auto& cluster = in_progress_clusters_.at(cluster_idx);
+  for (const auto& visit_url : cluster.visit_urls) {
+    visit_url_to_cluster_map_.erase(visit_url);
+  }
+  for (const auto visit_id : cluster.visit_ids) {
+    visit_id_to_cluster_map_.erase(visit_id);
+  }
+
+  // TODO(b/259466296): Kick off persisting keywords and prominence bits.
+
+  in_progress_clusters_.erase(cluster_idx);
+}
+
+void ContextClustererHistoryServiceObserver::OverrideClockForTesting(
+    const base::Clock* clock) {
+  clock_ = clock;
+}
+
+}  // namespace history_clusters
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.h b/components/history_clusters/core/context_clusterer_history_service_observer.h
new file mode 100644
index 0000000..1afce648
--- /dev/null
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.h
@@ -0,0 +1,125 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CLUSTERS_CORE_CONTEXT_CLUSTERER_HISTORY_SERVICE_OBSERVER_H_
+#define COMPONENTS_HISTORY_CLUSTERS_CORE_CONTEXT_CLUSTERER_HISTORY_SERVICE_OBSERVER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/time/clock.h"
+#include "base/timer/timer.h"
+#include "components/history/core/browser/history_service_observer.h"
+
+class TemplateURLService;
+
+namespace history {
+class HistoryService;
+}  // namespace history
+
+namespace optimization_guide {
+class NewOptimizationGuideDecider;
+}  // namespace optimization_guide
+
+namespace history_clusters {
+
+// Information required for determine pending cluster.
+struct InProgressCluster {
+  InProgressCluster();
+  ~InProgressCluster();
+  InProgressCluster(const InProgressCluster&);
+
+  // The visit IDs that were added to this in-progress cluster.
+  std::vector<history::VisitID> visit_ids;
+  // The normalized URLs that are a part of this in-progress cluster.
+  base::flat_set<std::string> visit_urls;
+  // The visit time of the last visit added to this in-progress cluster.
+  base::Time last_visit_time;
+  // The search terms associated with this in-progress cluster. It will only be
+  // set once if a search visit is part of this in-progress cluster.
+  std::u16string search_terms;
+};
+
+// A HistoryServiceObserver responsible for grouping visits into clusters.
+//
+// This groups visits together based on their navigation graph (previous visit,
+// forward-back, reload, etc.). It is responsible for determining when a cluster
+// is closed based on navigational factors as well as a timer that regularly
+// cleans up in-progress clusters.
+//
+// Still todo are to persist the visits to the clusters database as they come
+// in. After this is fully rolled out, there should not be any concept of
+// "incomplete" visits and that the on-device clustering backend will receive a
+// vector of clusters to combine or add metadata to.
+class ContextClustererHistoryServiceObserver
+    : public history::HistoryServiceObserver {
+ public:
+  ContextClustererHistoryServiceObserver(
+      history::HistoryService* history_service,
+      TemplateURLService* template_url_service,
+      optimization_guide::NewOptimizationGuideDecider*
+          optimization_guide_decider);
+  ~ContextClustererHistoryServiceObserver() override;
+
+  // history::HistoryServiceObserver:
+  void OnURLVisited(history::HistoryService* history_service,
+                    const history::URLRow& url_row,
+                    const history::VisitRow& visit_row) override;
+  void OnURLsDeleted(history::HistoryService* history_service,
+                     const history::DeletionInfo& deletion_info) override;
+
+ private:
+  friend class ContextClustererHistoryServiceObserverTest;
+
+  // Cleans up clusters that have not been interacted with for awhile.
+  void CleanUpClusters();
+
+  // Finalizes the cluster with index, `cluster_idx`.
+  void FinalizeCluster(int64_t cluster_idx);
+
+  // Overrides `clock_` for testing.
+  void OverrideClockForTesting(const base::Clock* clock);
+
+  // Returns the number of clusters created since the start of the session.
+  int64_t num_clusters_created() const { return cluster_idx_counter_; }
+
+  // Mapping from cluster ID to the contents of the in-progress cluster.
+  std::map<int64_t, InProgressCluster> in_progress_clusters_;
+
+  // Mapping from visit ID to the in-progress cluster ID it belongs to.
+  std::map<history::VisitID, int64_t> visit_id_to_cluster_map_;
+
+  // Mapping from normalized URL spec to the in-progress cluster ID it belongs
+  // to.
+  std::map<std::string, int64_t> visit_url_to_cluster_map_;
+
+  // A running counter that is used to index the in-progress clusters.
+  int64_t cluster_idx_counter_ = 0;
+
+  // Used to invoke `CleanUpClusters()` periodically.
+  base::RepeatingTimer clean_up_clusters_repeating_timer_;
+
+  // The Template URL Service used to determine if a visit is a search visit.
+  raw_ptr<TemplateURLService> template_url_service_;
+
+  // The Optimization Guide decider used to determine whether to include a visit
+  // in a cluster.
+  raw_ptr<optimization_guide::NewOptimizationGuideDecider>
+      optimization_guide_decider_;
+
+  // The clock used to schedule the clean up of clusters.
+  raw_ptr<const base::Clock> clock_;
+
+  // Tracks the observed history service, for cleanup.
+  base::ScopedObservation<history::HistoryService,
+                          history::HistoryServiceObserver>
+      history_service_observation_{this};
+};
+
+}  // namespace history_clusters
+
+#endif  // COMPONENTS_HISTORY_CLUSTERS_CORE_CONTEXT_CLUSTERER_HISTORY_SERVICE_OBSERVER_H_
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
new file mode 100644
index 0000000..ef4999de
--- /dev/null
+++ b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history_clusters/core/context_clusterer_history_service_observer.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "components/history_clusters/core/config.h"
+#include "components/optimization_guide/core/new_optimization_guide_decider.h"
+#include "components/search_engines/template_url_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace history_clusters {
+
+namespace {
+
+history::URLRows CreateURLRows(const std::vector<GURL>& urls) {
+  history::URLRows url_rows;
+  for (const auto& url : urls) {
+    url_rows.emplace_back(history::URLRow(url));
+  }
+  return url_rows;
+}
+
+class TestOptimizationGuideDecider
+    : public optimization_guide::NewOptimizationGuideDecider {
+ public:
+  TestOptimizationGuideDecider() = default;
+  ~TestOptimizationGuideDecider() override = default;
+
+  void RegisterOptimizationTypes(
+      const std::vector<optimization_guide::proto::OptimizationType>&
+          optimization_types) override {
+    ASSERT_EQ(optimization_types.size(), 1u);
+    ASSERT_EQ(optimization_guide::proto::HISTORY_CLUSTERS,
+              optimization_types[0]);
+  }
+
+  void CanApplyOptimization(
+      const GURL& url,
+      optimization_guide::proto::OptimizationType optimization_type,
+      optimization_guide::OptimizationGuideDecisionCallback callback) override {
+    NOTREACHED();
+  }
+
+  optimization_guide::OptimizationGuideDecision CanApplyOptimization(
+      const GURL& url,
+      optimization_guide::proto::OptimizationType optimization_type,
+      optimization_guide::OptimizationMetadata* optimization_metadata)
+      override {
+    DCHECK_EQ(optimization_guide::proto::HISTORY_CLUSTERS, optimization_type);
+    return url.host() == "shouldskip.com"
+               ? optimization_guide::OptimizationGuideDecision::kFalse
+               : optimization_guide::OptimizationGuideDecision::kTrue;
+  }
+
+  void CanApplyOptimizationOnDemand(
+      const std::vector<GURL>& urls,
+      const base::flat_set<optimization_guide::proto::OptimizationType>&
+          optimization_types,
+      optimization_guide::proto::RequestContext request_context,
+      optimization_guide::OnDemandOptimizationGuideDecisionRepeatingCallback
+          callback) override {}
+};
+
+const TemplateURLService::Initializer kTemplateURLData[] = {
+    {"default-engine.com", "http://default-engine.com/search?q={searchTerms}",
+     "Default"},
+    {"non-default-engine.com", "http://non-default-engine.com?q={searchTerms}",
+     "Not Default"},
+};
+
+}  // namespace
+
+class ContextClustererHistoryServiceObserverTest : public testing::Test {
+ public:
+  ContextClustererHistoryServiceObserverTest() = default;
+  ~ContextClustererHistoryServiceObserverTest() override = default;
+
+  void SetUp() override {
+    // Set up a simple template URL service with a default search engine.
+    template_url_service_ = std::make_unique<TemplateURLService>(
+        kTemplateURLData, std::size(kTemplateURLData));
+
+    optimization_guide_decider_ =
+        std::make_unique<TestOptimizationGuideDecider>();
+
+    // Instantiate observer.
+    observer_ = std::make_unique<ContextClustererHistoryServiceObserver>(
+        /*history_service=*/nullptr, template_url_service_.get(),
+        optimization_guide_decider_.get());
+    observer_->OverrideClockForTesting(task_environment_.GetMockClock());
+  }
+
+  // Simulates a visit to URL.
+  void VisitURL(const GURL& url,
+                history::VisitID visit_id,
+                base::Time visit_time,
+                history::VisitID opener_visit = history::kInvalidVisitID,
+                history::VisitID referring_visit = history::kInvalidVisitID,
+                bool is_known_to_sync = false) {
+    history::URLRow url_row(url);
+    history::VisitRow new_visit;
+    new_visit.visit_id = visit_id;
+    new_visit.visit_time = visit_time;
+    new_visit.opener_visit = opener_visit;
+    new_visit.referring_visit = referring_visit;
+    new_visit.is_known_to_sync = is_known_to_sync;
+    observer_->OnURLVisited(/*history_service=*/nullptr, url_row, new_visit);
+  }
+
+  // Simulates deleting `urls` from history. If `urls` is empty, we will
+  // simulate deleting all history.
+  void DeleteHistory(const std::vector<GURL>& urls) {
+    history::DeletionInfo deletion_info =
+        urls.empty() ? history::DeletionInfo::ForAllHistory()
+                     : history::DeletionInfo::ForUrls(CreateURLRows(urls),
+                                                      /*favicon_urls=*/{});
+    observer_->OnURLsDeleted(/*history_service=*/nullptr, deletion_info);
+  }
+
+  // Move clock forward by `time_delta`.
+  void MoveClockForwardBy(base::TimeDelta time_delta) {
+    task_environment_.FastForwardBy(time_delta);
+    task_environment_.RunUntilIdle();
+  }
+
+  // Returns the number of clusters created by |observer_|.
+  int64_t GetNumClustersCreated() const {
+    return observer_->num_clusters_created();
+  }
+
+  // Returns the current time of this task environment's mock clock.
+  base::Time Now() { return task_environment_.GetMockClock()->Now(); }
+
+ private:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  std::unique_ptr<TemplateURLService> template_url_service_;
+  std::unique_ptr<TestOptimizationGuideDecider> optimization_guide_decider_;
+  std::unique_ptr<ContextClustererHistoryServiceObserver> observer_;
+};
+
+TEST_F(ContextClustererHistoryServiceObserverTest, ClusterOneVisit) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest,
+       ClusterTwoVisitsTiedByReferringVisit) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+  VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(123),
+           /*opener_visit=*/history::kInvalidVisitID, /*referring_visit=*/1);
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest,
+       ClusterTwoVisitsTiedByOpenerVisit) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+  VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(123),
+           /*opener_visit=*/1, /*referring_visit=*/history::kInvalidVisitID);
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest,
+       ClusterTwoVisitsOpenerTakesPrecedenceOverReferrer) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+  VisitURL(GURL("https://hasopenerbutbadreferrer.com"), 2,
+           base::Time::FromTimeT(123),
+           /*opener_visit=*/1, /*referring_visit=*/6);
+  VisitURL(GURL("https://hasbadopenerbutgoodreferrer.com"), 3,
+           base::Time::FromTimeT(123),
+           /*opener_visit=*/6, /*referring_visit=*/2);
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, ClusterTwoVisitsTiedByURL) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+  VisitURL(GURL("https://example.com"), 2, base::Time::FromTimeT(123),
+           /*opener_visit=*/history::kInvalidVisitID,
+           /*referring_visit=*/history::kInvalidVisitID);
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest,
+       ClusterTwoVisitsTiedBySearchURL) {
+  VisitURL(GURL("http://default-engine.com/search?q=foo"), 1,
+           base::Time::FromTimeT(123));
+  VisitURL(GURL("http://default-engine.com/search?q=foo#whatever"), 2,
+           base::Time::FromTimeT(123),
+           /*opener_visit=*/history::kInvalidVisitID,
+           /*referring_visit=*/history::kInvalidVisitID);
+
+  EXPECT_EQ(1, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, SplitClusterOnSearchTerm) {
+  VisitURL(GURL("http://default-engine.com/search?q=foo"), 1,
+           base::Time::FromTimeT(123));
+  VisitURL(GURL("http://default-engine.com/search?q=otherterm"), 2,
+           base::Time::FromTimeT(123),
+           /*opener_visit=*/1);
+
+  EXPECT_EQ(2, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest,
+       SplitClusterOnNavigationTime) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+  VisitURL(GURL("https://example.com/2"), 2,
+           base::Time::FromTimeT(123) + base::Milliseconds(1) +
+               GetConfig().cluster_navigation_time_cutoff,
+           /*opener_visit=*/1, /*referring_visit=*/history::kInvalidVisitID);
+
+  EXPECT_EQ(2, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, SkipsSyncedVisits) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123),
+           history::kInvalidVisitID, history::kInvalidVisitID,
+           /*is_known_to_sync=*/true);
+
+  EXPECT_EQ(0, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, SkipsBlocklistedHost) {
+  VisitURL(GURL("https://shouldskip.com"), 1, base::Time::FromTimeT(123));
+
+  EXPECT_EQ(0, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, MultipleClusters) {
+  VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(1));
+  VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(2), 1);
+  VisitURL(GURL("https://whatever.com"), 3, base::Time::FromTimeT(3));
+  VisitURL(GURL("https://example.com"), 4, base::Time::FromTimeT(4));
+  VisitURL(GURL("https://nonexistentreferrer.com"), 10,
+           base::Time::FromTimeT(10), 6);
+
+  // The clusters should be (1, 2, 4), (3), (10).
+  EXPECT_EQ(3, GetNumClustersCreated());
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, CleansUpClusters) {
+  {
+    base::HistogramTester histogram_tester;
+
+    // Simulate a now time that is "old"-ish to make sure these get cleaned up
+    // appropriately.
+    base::Time now =
+        Now() - GetConfig().cluster_navigation_time_cutoff - base::Minutes(1);
+    VisitURL(GURL("https://example.com"), 1, now);
+    VisitURL(GURL("https://example.com/2"), 2, now + base::Milliseconds(2), 1);
+    VisitURL(GURL("https://whatever.com"), 3, now + base::Milliseconds(4));
+    VisitURL(GURL("https://example.com"), 4, now + base::Milliseconds(10));
+    // Make sure this last cluster doesn't get cleaned up, so use the actual
+    // "Now" value.
+    VisitURL(
+        GURL("https://nonexistentreferrer.com"), 10,
+        Now() + base::Minutes(
+                    GetConfig().cluster_navigation_time_cutoff.InMinutes() / 2),
+        6);
+    // The clusters should be (1, 2, 4), (3), (10).
+    EXPECT_EQ(3, GetNumClustersCreated());
+
+    // Force a cleanup pass.
+    MoveClockForwardBy(GetConfig().context_clustering_clean_up_duration);
+
+    histogram_tester.ExpectUniqueSample(
+        "History.Clusters.ContextClusterer.NumClusters.AtCleanUp", 3, 1);
+    histogram_tester.ExpectUniqueSample(
+        "History.Clusters.ContextClusterer.NumClusters.CleanedUp", 2, 1);
+    // Should not finalize cluster with visit 10.
+    histogram_tester.ExpectUniqueSample(
+        "History.Clusters.ContextClusterer.NumClusters.PostCleanUp", 1, 1);
+  }
+
+  // This should create a new cluster.
+  VisitURL(GURL("https://example.com"), 11, Now(), 4);
+  // This should connect to the cluster with visit 10.
+  VisitURL(GURL("https://newvisit.com"), 12, Now(), 10);
+
+  // Expect only one more cluster to be created, which makes 4 total.
+  EXPECT_EQ(4, GetNumClustersCreated());
+
+  // Make sure everything is cleaned up eventually.
+  {
+    base::HistogramTester histogram_tester;
+
+    MoveClockForwardBy(2 * GetConfig().cluster_navigation_time_cutoff);
+
+    histogram_tester.ExpectBucketCount(
+        "History.Clusters.ContextClusterer.NumClusters.PostCleanUp", 0, 1);
+  }
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, DeleteAllHistory) {
+  base::HistogramTester histogram_tester;
+
+  VisitURL(GURL("https://example.com"), 1, Now());
+  VisitURL(GURL("https://example.com/2"), 2, Now());
+
+  // Simulate deleting all history.
+  DeleteHistory(/*urls=*/{});
+
+  // Force a cleanup pass.
+  MoveClockForwardBy(GetConfig().context_clustering_clean_up_duration);
+
+  // There should be nothing to clean up so it shouldn't even initiate a clean
+  // up pass.
+  histogram_tester.ExpectTotalCount(
+      "History.Clusters.ContextClusterer.NumClusters.AtCleanUp", 0);
+  histogram_tester.ExpectTotalCount(
+      "History.Clusters.ContextClusterer.NumClusters.CleanedUp", 0);
+  histogram_tester.ExpectTotalCount(
+      "History.Clusters.ContextClusterer.NumClusters.PostCleanUp", 0);
+}
+
+TEST_F(ContextClustererHistoryServiceObserverTest, DeleteSelectURLs) {
+  base::HistogramTester histogram_tester;
+
+  VisitURL(GURL("https://example.com"), 1, Now());
+  VisitURL(GURL("https://example.com/2"), 2, Now());
+  VisitURL(GURL("https://default-engine.com/search?q=foo"), 3, Now());
+  VisitURL(GURL("https://default-engine.com/search?q=foo#whatever"), 4, Now());
+
+  // Simulate deleting one URL and one search URL.
+  DeleteHistory({GURL("https://example.com"),
+                 GURL("https://default-engine.com/search?q=foo#whateverelse")});
+
+  // Force a cleanup pass.
+  MoveClockForwardBy(GetConfig().context_clustering_clean_up_duration);
+
+  // There should be 1 cluster untouched by the deletion.
+  histogram_tester.ExpectUniqueSample(
+      "History.Clusters.ContextClusterer.NumClusters.AtCleanUp", 1, 1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Clusters.ContextClusterer.NumClusters.CleanedUp", 0, 1);
+  histogram_tester.ExpectUniqueSample(
+      "History.Clusters.ContextClusterer.NumClusters.PostCleanUp", 1, 1);
+}
+
+}  // namespace history_clusters
\ No newline at end of file
diff --git a/components/history_clusters/core/features.cc b/components/history_clusters/core/features.cc
index f60b27e..23bc207 100644
--- a/components/history_clusters/core/features.cc
+++ b/components/history_clusters/core/features.cc
@@ -81,6 +81,10 @@
              "JourneysIncludeSyncedVisits",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kHistoryClustersNavigationContextClustering,
+             "HistoryClustersNavigationContextClustering",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace internal
 
 BASE_FEATURE(kJourneysSurveyForHistoryEntrypoint,
diff --git a/components/history_clusters/core/features.h b/components/history_clusters/core/features.h
index 251d45da..3ee66b98 100644
--- a/components/history_clusters/core/features.h
+++ b/components/history_clusters/core/features.h
@@ -67,6 +67,10 @@
 // Enables visits from other synced devices to be included in clusters.
 BASE_DECLARE_FEATURE(kJourneysIncludeSyncedVisits);
 
+// Enables context clustering to be performed at navigation time rather than in
+// batches.
+BASE_DECLARE_FEATURE(kHistoryClustersNavigationContextClustering);
+
 // Order consistently with config.h.
 
 }  // namespace internal
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index f36aec0..d192983 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -58,12 +58,16 @@
     optimization_guide::EntityMetadataProvider* entity_metadata_provider,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     site_engagement::SiteEngagementScoreProvider* engagement_score_provider,
+    TemplateURLService* template_url_service,
     optimization_guide::NewOptimizationGuideDecider* optimization_guide_decider)
     : is_journeys_enabled_(
           GetConfig().is_journeys_enabled_no_locale_check &&
           IsApplicationLocaleSupportedByJourneys(application_locale)),
       history_service_(history_service),
-      visit_deletion_observer_(this) {
+      visit_deletion_observer_(this),
+      context_clusterer_observer_(history_service,
+                                  template_url_service,
+                                  optimization_guide_decider) {
   DCHECK(history_service_);
 
   visit_deletion_observer_.AttachToHistoryService(history_service);
diff --git a/components/history_clusters/core/history_clusters_service.h b/components/history_clusters/core/history_clusters_service.h
index 2ae92e1..96ab6d32 100644
--- a/components/history_clusters/core/history_clusters_service.h
+++ b/components/history_clusters/core/history_clusters_service.h
@@ -25,12 +25,15 @@
 #include "components/history/core/browser/history_service_observer.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history_clusters/core/clustering_backend.h"
+#include "components/history_clusters/core/context_clusterer_history_service_observer.h"
 #include "components/history_clusters/core/history_clusters_service_task_get_most_recent_clusters.h"
 #include "components/history_clusters/core/history_clusters_service_task_update_clusters.h"
 #include "components/history_clusters/core/history_clusters_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
+class TemplateURLService;
+
 namespace optimization_guide {
 class EntityMetadataProvider;
 class NewOptimizationGuideDecider;
@@ -93,6 +96,7 @@
       optimization_guide::EntityMetadataProvider* entity_metadata_provider,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       site_engagement::SiteEngagementScoreProvider* engagement_score_provider,
+      TemplateURLService* template_url_service,
       optimization_guide::NewOptimizationGuideDecider*
           optimization_guide_decider);
   HistoryClustersService(const HistoryClustersService&) = delete;
@@ -282,6 +286,8 @@
 
   VisitDeletionObserver visit_deletion_observer_;
 
+  ContextClustererHistoryServiceObserver context_clusterer_observer_;
+
   // Weak pointers issued from this factory never get invalidated before the
   // service is destroyed.
   base::WeakPtrFactory<HistoryClustersService> weak_ptr_factory_{this};
diff --git a/components/history_clusters/core/history_clusters_service_unittest.cc b/components/history_clusters/core/history_clusters_service_unittest.cc
index e8b61c3..05fe8ca 100644
--- a/components/history_clusters/core/history_clusters_service_unittest.cc
+++ b/components/history_clusters/core/history_clusters_service_unittest.cc
@@ -110,6 +110,7 @@
         /*entity_metadata_provider=*/nullptr,
         /*url_loader_factory=*/nullptr,
         /*engagement_score_provider=*/nullptr,
+        /*template_url_service=*/nullptr,
         /*optimization_guide_decider=*/nullptr);
 
     history_clusters_service_test_api_ =
diff --git a/components/history_clusters/history_clusters_internals/resources/BUILD.gn b/components/history_clusters/history_clusters_internals/resources/BUILD.gn
index 208a1c3..ae159c9 100644
--- a/components/history_clusters/history_clusters_internals/resources/BUILD.gn
+++ b/components/history_clusters/history_clusters_internals/resources/BUILD.gn
@@ -33,7 +33,7 @@
 }
 
 copy("copy_mojo") {
-  deps = [ "//components/history_clusters/history_clusters_internals/webui:mojo_bindings_webui_js" ]
+  deps = [ "//components/history_clusters/history_clusters_internals/webui:mojo_bindings_js__generator" ]
   mojom_folder = "$root_gen_dir/mojom-webui/components/history_clusters/history_clusters_internals/webui/"
   sources = [ "$mojom_folder/history_clusters_internals.mojom-webui.js" ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
diff --git a/components/omnibox/browser/actions/history_clusters_action_unittest.cc b/components/omnibox/browser/actions/history_clusters_action_unittest.cc
index 2ae6c2f5..f68aff6 100644
--- a/components/omnibox/browser/actions/history_clusters_action_unittest.cc
+++ b/components/omnibox/browser/actions/history_clusters_action_unittest.cc
@@ -90,6 +90,7 @@
         /*entity_metadata_provider=*/nullptr,
         /*url_loader_factory=*/nullptr,
         /*engagement_score_provider=*/nullptr,
+        /*template_url_service=*/nullptr,
         /*optimization_guide_decider=*/nullptr);
 
     history_clusters_service_test_api_ =
diff --git a/components/omnibox/browser/history_cluster_provider_unittest.cc b/components/omnibox/browser/history_cluster_provider_unittest.cc
index 540d1a7..ee880cc7 100644
--- a/components/omnibox/browser/history_cluster_provider_unittest.cc
+++ b/components/omnibox/browser/history_cluster_provider_unittest.cc
@@ -56,6 +56,7 @@
             /*entity_metadata_provider=*/nullptr,
             /*url_loader_factory=*/nullptr,
             /*engagement_score_provider=*/nullptr,
+            /*template_url_service=*/nullptr,
             /*optimization_guide_decider=*/nullptr);
 
     history_clusters_service_test_api_ =
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index ebc0b648..4148160 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -268,6 +268,8 @@
 
 static_library("prediction") {
   sources = [
+    "model_store_metadata_entry.cc",
+    "model_store_metadata_entry.h",
     "prediction_manager.cc",
     "prediction_manager.h",
     "prediction_model_download_manager.cc",
@@ -275,6 +277,8 @@
     "prediction_model_download_observer.h",
     "prediction_model_override.cc",
     "prediction_model_override.h",
+    "prediction_model_store.cc",
+    "prediction_model_store.h",
   ]
   deps = [
     "//components/crx_file",
@@ -388,6 +392,7 @@
     "prediction_manager_unittest.cc",
     "prediction_model_download_manager_unittest.cc",
     "prediction_model_fetcher_unittest.cc",
+    "prediction_model_store_unittest.cc",
     "push_notification_manager_unittest.cc",
     "store_update_data_unittest.cc",
     "url_pattern_with_wildcards_unittest.cc",
diff --git a/components/optimization_guide/core/model_store_metadata_entry.cc b/components/optimization_guide/core/model_store_metadata_entry.cc
new file mode 100644
index 0000000..a89439bb
--- /dev/null
+++ b/components/optimization_guide/core/model_store_metadata_entry.cc
@@ -0,0 +1,104 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/model_store_metadata_entry.h"
+
+#include "base/files/file_path.h"
+#include "base/json/values_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/optimization_guide/core/model_util.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "components/optimization_guide/core/optimization_guide_util.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/prefs/pref_service.h"
+#include "model_store_metadata_entry.h"
+
+namespace optimization_guide {
+
+namespace {
+
+// Key names for the metadata entries.
+const char kKeyModelBaseDir[] = "mbd";
+const char kKeyExpiryTime[] = "et";
+const char kKeyKeepBeyondValidDuration[] = "kbvd";
+
+}  // namespace
+
+// static
+absl::optional<ModelStoreMetadataEntry>
+ModelStoreMetadataEntry::GetModelMetadataEntryIfExists(
+    PrefService* local_state,
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key) {
+  auto* metadata_target =
+      local_state->GetDict(prefs::localstate::kModelStoreMetadata)
+          .FindDict(
+              base::NumberToString(static_cast<int>(optimization_target)));
+  if (!metadata_target)
+    return absl::nullopt;
+  auto* metadata_entry =
+      metadata_target->FindDict(GetModelCacheKeyHash(model_cache_key));
+  if (!metadata_entry)
+    return absl::nullopt;
+  return ModelStoreMetadataEntry(metadata_entry);
+}
+
+ModelStoreMetadataEntry::ModelStoreMetadataEntry(
+    const base::Value::Dict* metadata_entry)
+    : metadata_entry_(metadata_entry) {}
+
+ModelStoreMetadataEntry::~ModelStoreMetadataEntry() = default;
+
+absl::optional<base::FilePath> ModelStoreMetadataEntry::GetModelBaseDir()
+    const {
+  return base::ValueToFilePath(metadata_entry_->Find(kKeyModelBaseDir));
+}
+
+base::Time ModelStoreMetadataEntry::GetExpiryTime() const {
+  return base::ValueToTime(metadata_entry_->Find(kKeyExpiryTime))
+      .value_or(base::Time::Now() + features::StoredModelsValidDuration());
+}
+
+bool ModelStoreMetadataEntry::GetKeepBeyondValidDuration() const {
+  return metadata_entry_->FindBool(kKeyKeepBeyondValidDuration).value_or(false);
+}
+
+void ModelStoreMetadataEntry::SetMetadataEntry(
+    const base::Value::Dict* metadata_entry) {
+  metadata_entry_ = metadata_entry;
+}
+
+ModelStoreMetadataEntryUpdater::ModelStoreMetadataEntryUpdater(
+    PrefService* local_state,
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key)
+    : ModelStoreMetadataEntry(/*metadata_entry=*/nullptr),
+      pref_updater_(local_state, prefs::localstate::kModelStoreMetadata) {
+  auto* metadata_target = pref_updater_->EnsureDict(
+      base::NumberToString(static_cast<int>(optimization_target)));
+  metadata_entry_updater_ =
+      metadata_target->EnsureDict(GetModelCacheKeyHash(model_cache_key));
+  SetMetadataEntry(metadata_entry_updater_);
+}
+
+void ModelStoreMetadataEntryUpdater::SetModelBaseDir(
+    base::FilePath model_base_dir) {
+  metadata_entry_updater_->Set(kKeyModelBaseDir,
+                               base::FilePathToValue(model_base_dir));
+}
+
+void ModelStoreMetadataEntryUpdater::SetExpiryTime(base::Time expiry_time) {
+  metadata_entry_updater_->Set(kKeyExpiryTime, base::TimeToValue(expiry_time));
+}
+
+void ModelStoreMetadataEntryUpdater::SetKeepBeyondValidDuration(
+    bool keep_beyond_valid_duration) {
+  metadata_entry_updater_->Set(kKeyKeepBeyondValidDuration,
+                               keep_beyond_valid_duration);
+}
+
+}  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/core/model_store_metadata_entry.h b/components/optimization_guide/core/model_store_metadata_entry.h
new file mode 100644
index 0000000..ff7731f
--- /dev/null
+++ b/components/optimization_guide/core/model_store_metadata_entry.h
@@ -0,0 +1,83 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_STORE_METADATA_ENTRY_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_STORE_METADATA_ENTRY_H_
+
+#include "base/values.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class FilePath;
+class Time;
+}  // namespace base
+
+class PrefService;
+
+namespace optimization_guide {
+
+// Encapsulates the lightweight metadata entry that is stored in local state
+// prefs for one model in the model store. The model is represented by the key
+// pair OptimizationTarget and hash of ModelCacheKey.
+class ModelStoreMetadataEntry {
+ public:
+  // Returns the metadata entry in the store if it exists.
+  static absl::optional<ModelStoreMetadataEntry> GetModelMetadataEntryIfExists(
+      PrefService* local_state,
+      proto::OptimizationTarget optimization_target,
+      const proto::ModelCacheKey& model_cache_key);
+
+  ModelStoreMetadataEntry& operator=(const ModelStoreMetadataEntry&) = delete;
+  ~ModelStoreMetadataEntry();
+
+  // Gets the model base dir where the model files, its additional files
+  // and the model info files are stored.
+  absl::optional<base::FilePath> GetModelBaseDir() const;
+
+  // Gets the expiry time.
+  base::Time GetExpiryTime() const;
+
+  // Gets whether the model should be kept beyond the expiry duration.
+  bool GetKeepBeyondValidDuration() const;
+
+ protected:
+  explicit ModelStoreMetadataEntry(const base::Value::Dict* metadata_entry);
+
+  void SetMetadataEntry(const base::Value::Dict* metadata_entry);
+
+ private:
+  // The root metadata entry for this model.
+  const base::Value::Dict* metadata_entry_;
+};
+
+// The pref updater for ModelStoreMetadataEntry.
+class ModelStoreMetadataEntryUpdater : public ModelStoreMetadataEntry {
+ public:
+  // Returns the metadata entry in the store, creating it if it does not exist.
+  ModelStoreMetadataEntryUpdater(PrefService* local_state,
+                                 proto::OptimizationTarget optimization_target,
+                                 const proto::ModelCacheKey& model_cache_key);
+
+  ModelStoreMetadataEntryUpdater(const ModelStoreMetadataEntryUpdater&) =
+      delete;
+  ModelStoreMetadataEntryUpdater& operator=(
+      const ModelStoreMetadataEntryUpdater&) = delete;
+
+  // The setters for the various model metadata.
+  void SetModelBaseDir(base::FilePath model_base_dir);
+  void SetKeepBeyondValidDuration(bool keep_beyond_valid_duration);
+  void SetExpiryTime(base::Time expiry_time);
+
+ private:
+  // The root metadata entry that is linked with the |pref_updater_|.
+  base::Value::Dict* metadata_entry_updater_;
+
+  ScopedDictPrefUpdate pref_updater_;
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_STORE_METADATA_ENTRY_H_
diff --git a/components/optimization_guide/core/model_util.cc b/components/optimization_guide/core/model_util.cc
index fc92be5..ab3d441 100644
--- a/components/optimization_guide/core/model_util.cc
+++ b/components/optimization_guide/core/model_util.cc
@@ -7,8 +7,10 @@
 #include "base/base64.h"
 #include "base/containers/flat_set.h"
 #include "base/files/file_util.h"
+#include "base/hash/legacy_hash.h"
 #include "base/logging.h"
 #include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -197,4 +199,14 @@
   return true;
 }
 
+std::string GetModelCacheKeyHash(proto::ModelCacheKey model_cache_key) {
+  std::string bytes;
+  model_cache_key.SerializeToString(&bytes);
+  uint64_t hash =
+      base::legacy::CityHash64(base::as_bytes(base::make_span(bytes)));
+  // Convert the hash to hex encoding and not as base64 and other encodings,
+  // since it will be used as filepath names.
+  return base::HexEncode(base::as_bytes(base::make_span(&hash, 1)));
+}
+
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_util.h b/components/optimization_guide/core/model_util.h
index aed1703b..4892b05 100644
--- a/components/optimization_guide/core/model_util.h
+++ b/components/optimization_guide/core/model_util.h
@@ -50,6 +50,10 @@
 // Checks all the files in |file_paths_to_check| exists.
 bool CheckAllPathsExist(const std::vector<base::FilePath>& file_paths_to_check);
 
+// Returns the hash of |model_cache_key| that can be used as key in a
+// persistent dict, or can be used as file paths.
+std::string GetModelCacheKeyHash(proto::ModelCacheKey model_cache_key);
+
 }  // namespace optimization_guide
 
 #endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_UTIL_H_
diff --git a/components/optimization_guide/core/model_util_unittest.cc b/components/optimization_guide/core/model_util_unittest.cc
index f2f4bb3b..43fe104 100644
--- a/components/optimization_guide/core/model_util_unittest.cc
+++ b/components/optimization_guide/core/model_util_unittest.cc
@@ -6,6 +6,8 @@
 
 #include "base/base64.h"
 #include "base/command_line.h"
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_test_util.h"
@@ -21,6 +23,12 @@
 const char kOtherAbsoluteFilePath[] = "/other/abs/file/path";
 #endif
 
+proto::ModelCacheKey CreateModelCacheKey(const std::string& locale) {
+  proto::ModelCacheKey model_cache_key;
+  model_cache_key.set_locale(locale);
+  return model_cache_key;
+}
+
 }  // namespace
 
 TEST(ModelUtilTest, GetModelOverrideForOptimizationTargetSwitchNotSet) {
@@ -169,4 +177,14 @@
   EXPECT_EQ("sometypeurl", file_path_and_metadata->second->type_url());
 }
 
+TEST(ModelUtilTest, ModelCacheKeyHash) {
+  EXPECT_EQ(GetModelCacheKeyHash(CreateModelCacheKey("en-US")),
+            GetModelCacheKeyHash(CreateModelCacheKey("en-US")));
+  EXPECT_NE(GetModelCacheKeyHash(CreateModelCacheKey("en-US")),
+            GetModelCacheKeyHash(CreateModelCacheKey("en-UK")));
+  EXPECT_TRUE(
+      base::ranges::all_of(GetModelCacheKeyHash(CreateModelCacheKey("en-US")),
+                           [](char ch) { return base::IsHexDigit(ch); }));
+}
+
 }  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/core/optimization_guide_prefs.cc b/components/optimization_guide/core/optimization_guide_prefs.cc
index 9d070a7..530d34d 100644
--- a/components/optimization_guide/core/optimization_guide_prefs.cc
+++ b/components/optimization_guide/core/optimization_guide_prefs.cc
@@ -54,6 +54,14 @@
 const char kStoreFilePathsToDelete[] =
     "optimization_guide.store_file_paths_to_delete";
 
+namespace localstate {
+
+// A dictionary pref that stores the lightweight metadata of all the models in
+// the store, keyed by the optimization target and ModelCacheKey.
+const char kModelStoreMetadata[] = "optimization_guide.model_store_metadata";
+
+}  // namespace localstate
+
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterInt64Pref(
       kHintsFetcherLastFetchAttempt,
@@ -76,5 +84,9 @@
                                    PrefRegistry::LOSSY_PREF);
 }
 
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(localstate::kModelStoreMetadata);
+}
+
 }  // namespace prefs
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_prefs.h b/components/optimization_guide/core/optimization_guide_prefs.h
index 13f28680..178fadb 100644
--- a/components/optimization_guide/core/optimization_guide_prefs.h
+++ b/components/optimization_guide/core/optimization_guide_prefs.h
@@ -10,6 +10,7 @@
 namespace optimization_guide {
 namespace prefs {
 
+// User profile prefs.
 extern const char kHintsFetcherLastFetchAttempt[];
 extern const char kModelAndFeaturesLastFetchAttempt[];
 extern const char kModelLastFetchSuccess[];
@@ -18,9 +19,19 @@
 extern const char kPreviouslyRegisteredOptimizationTypes[];
 extern const char kStoreFilePathsToDelete[];
 
+namespace localstate {
+
+// Local state prefs.
+extern const char kModelStoreMetadata[];
+
+}  // namespace localstate
+
 // Registers the optimization guide's prefs.
 void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
+// Registers the local state prefs.
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
+
 }  // namespace prefs
 }  // namespace optimization_guide
 
diff --git a/components/optimization_guide/core/prediction_manager.h b/components/optimization_guide/core/prediction_manager.h
index 2ecf3cc..0858c2a 100644
--- a/components/optimization_guide/core/prediction_manager.h
+++ b/components/optimization_guide/core/prediction_manager.h
@@ -290,8 +290,7 @@
   // The logger that plumbs the debug logs to the optimization guide
   // internals page. Not owned. Guaranteed to outlive |this|, since the logger
   // and |this| are owned by the optimization guide keyed service.
-  raw_ptr<OptimizationGuideLogger, DanglingUntriaged>
-      optimization_guide_logger_;
+  raw_ptr<OptimizationGuideLogger> optimization_guide_logger_;
 
   // A reference to the PrefService for this profile. Not owned.
   raw_ptr<PrefService> pref_service_ = nullptr;
diff --git a/components/optimization_guide/core/prediction_model_store.cc b/components/optimization_guide/core/prediction_model_store.cc
new file mode 100644
index 0000000..e2a576fb
--- /dev/null
+++ b/components/optimization_guide/core/prediction_model_store.cc
@@ -0,0 +1,214 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/prediction_model_store.h"
+
+#include "base/files/file_util.h"
+#include "base/guid.h"
+#include "base/rand_util.h"
+#include "base/task/thread_pool.h"
+#include "components/optimization_guide/core/model_store_metadata_entry.h"
+#include "components/optimization_guide/core/model_util.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
+#include "components/prefs/pref_service.h"
+#include "model_store_metadata_entry.h"
+
+namespace optimization_guide {
+
+namespace {
+
+// Returns the model info parsed from |model_info_path|.
+absl::optional<proto::ModelInfo> ParseModelInfoFromFile(
+    const base::FilePath& model_info_path) {
+  std::string binary_model_info;
+  if (!base::ReadFileToString(model_info_path, &binary_model_info))
+    return absl::nullopt;
+
+  proto::ModelInfo model_info;
+  if (!model_info.ParseFromString(binary_model_info))
+    return absl::nullopt;
+
+  DCHECK(model_info.has_version());
+  DCHECK(model_info.has_optimization_target());
+  return model_info;
+}
+
+// Returns all the model file paths for the model |model_info| in
+// |base_model_dir|.
+std::vector<base::FilePath> GetModelFilePaths(
+    const proto::ModelInfo& model_info,
+    const base::FilePath& base_model_dir) {
+  std::vector<base::FilePath> model_file_paths;
+  model_file_paths.emplace_back(
+      base_model_dir.Append(GetBaseFileNameForModels()));
+  model_file_paths.emplace_back(
+      base_model_dir.Append(GetBaseFileNameForModelInfo()));
+  for (const auto& additional_file : model_info.additional_files()) {
+    auto additional_filepath = StringToFilePath(additional_file.file_path());
+    if (!additional_filepath)
+      continue;
+    DCHECK(base_model_dir.IsParent(*additional_filepath));
+    model_file_paths.emplace_back(*additional_filepath);
+  }
+  return model_file_paths;
+}
+
+}  // namespace
+
+PredictionModelStore::PredictionModelStore(PrefService* local_state,
+                                           const base::FilePath& base_store_dir)
+    : local_state_(local_state),
+      base_store_dir_(base_store_dir),
+      background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
+  DCHECK(optimization_guide::features::IsInstallWideModelStoreEnabled());
+  DCHECK(local_state);
+  DCHECK(!base_store_dir.empty());
+}
+
+PredictionModelStore::~PredictionModelStore() = default;
+
+bool PredictionModelStore::HasModel(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return ModelStoreMetadataEntry::GetModelMetadataEntryIfExists(
+             local_state_, optimization_target, model_cache_key)
+      .has_value();
+}
+
+void PredictionModelStore::LoadModel(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key,
+    PredictionModelLoadedCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto metadata = ModelStoreMetadataEntry::GetModelMetadataEntryIfExists(
+      local_state_, optimization_target, model_cache_key);
+  if (!metadata) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+  if (!metadata->GetKeepBeyondValidDuration() &&
+      metadata->GetExpiryTime() <= base::Time::Now()) {
+    // TODO(b/244649670): Remove the invalid model.
+    std::move(callback).Run(nullptr);
+    return;
+  }
+  auto base_model_dir = metadata->GetModelBaseDir();
+  if (!base_model_dir) {
+    // TODO(b/244649670): Remove the invalid model.
+    std::move(callback).Run(nullptr);
+    return;
+  }
+  DCHECK(base_store_dir_.IsParent(*base_model_dir));
+
+  background_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(
+          &PredictionModelStore::LoadAndVerifyModelInBackgroundThread,
+          optimization_target, *base_model_dir),
+      base::BindOnce(&PredictionModelStore::OnModelLoaded,
+                     weak_ptr_factory_.GetWeakPtr(), optimization_target,
+                     model_cache_key, std::move(callback)));
+}
+
+// static
+std::unique_ptr<proto::PredictionModel>
+PredictionModelStore::LoadAndVerifyModelInBackgroundThread(
+    proto::OptimizationTarget optimization_target,
+    const base::FilePath& base_model_dir) {
+  auto model_info = ParseModelInfoFromFile(
+      base_model_dir.Append(GetBaseFileNameForModelInfo()));
+  if (!model_info) {
+    return nullptr;
+  }
+  DCHECK_EQ(optimization_target, model_info->optimization_target());
+  // Make sure the model file, the full modelinfo file and all additional
+  // files still exist.
+  auto file_paths_to_check = GetModelFilePaths(*model_info, base_model_dir);
+  if (!CheckAllPathsExist(file_paths_to_check)) {
+    return nullptr;
+  }
+  std::unique_ptr<proto::PredictionModel> model =
+      std::make_unique<proto::PredictionModel>();
+  *model->mutable_model_info() = *model_info;
+  model->mutable_model()->set_download_url(
+      FilePathToString(base_model_dir.Append(GetBaseFileNameForModels())));
+
+  return model;
+}
+
+void PredictionModelStore::OnModelLoaded(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key,
+    PredictionModelLoadedCallback callback,
+    std::unique_ptr<proto::PredictionModel> model) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!model) {
+    // TODO(b/244649670): Remove the invalid model.
+    std::move(callback).Run(nullptr);
+    return;
+  }
+  std::move(callback).Run(std::move(model));
+}
+
+void PredictionModelStore::UpdateModel(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key,
+    const proto::ModelInfo& model_info,
+    const base::FilePath& base_model_dir,
+    base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(model_info.has_version());
+  DCHECK_EQ(optimization_target, model_info.optimization_target());
+  DCHECK(base_store_dir_.IsParent(base_model_dir));
+
+  ModelStoreMetadataEntryUpdater metadata(local_state_, optimization_target,
+                                          model_cache_key);
+  metadata.SetExpiryTime(
+      base::Time::Now() +
+      (model_info.has_valid_duration()
+           ? base::Seconds(model_info.valid_duration().seconds())
+           : features::StoredModelsValidDuration()));
+  metadata.SetKeepBeyondValidDuration(model_info.keep_beyond_valid_duration());
+  metadata.SetModelBaseDir(base_model_dir);
+
+  background_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&CheckAllPathsExist,
+                     GetModelFilePaths(model_info, base_model_dir)),
+      base::BindOnce(&PredictionModelStore::OnModelUpdateVerified,
+                     weak_ptr_factory_.GetWeakPtr(), optimization_target,
+                     model_cache_key, std::move(callback)));
+}
+
+void PredictionModelStore::OnModelUpdateVerified(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key,
+    base::OnceClosure callback,
+    bool model_paths_exist) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!model_paths_exist) {
+    // TODO(b/244649670): Remove the invalid model.
+  }
+  std::move(callback).Run();
+}
+
+base::FilePath PredictionModelStore::GetBaseModelDirForModelCacheKey(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!base_store_dir_.empty());
+  auto base_model_dir = base_store_dir_
+                            .AppendASCII(base::NumberToString(
+                                static_cast<int>(optimization_target)))
+                            .AppendASCII(GetModelCacheKeyHash(model_cache_key));
+  return base_model_dir.AppendASCII(base::HexEncode(
+      base::as_bytes(base::make_span(base::RandBytesAsString(8)))));
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/prediction_model_store.h b/components/optimization_guide/core/prediction_model_store.h
new file mode 100644
index 0000000..60599d63
--- /dev/null
+++ b/components/optimization_guide/core/prediction_model_store.h
@@ -0,0 +1,114 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_STORE_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_STORE_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/optimization_guide/proto/models.pb.h"
+
+class PrefService;
+
+namespace optimization_guide {
+
+// The new model store that manages the optimization guide prediction models.
+// The model store is a key-value store where the optimization target,
+// ModelCacheKey can be together considered as the key and the value is the
+// model metadata and its files. For every model following are stored:
+//   * The prediction model file and its additional files are stored in the
+//   model dir.
+//   * The full model metadata as model_info.pb in the model dir.
+//   * Lightweight model metadata in the local state prefs, that is immediately
+//   needed for managing the store, and to avoid the full metadata read.
+// The model store is meant to be shared across profiles.
+class PredictionModelStore {
+ public:
+  using PredictionModelLoadedCallback =
+      base::OnceCallback<void(std::unique_ptr<proto::PredictionModel>)>;
+
+  PredictionModelStore(PrefService* local_state,
+                       const base::FilePath& base_store_dir);
+
+  PredictionModelStore(const PredictionModelStore&) = delete;
+  PredictionModelStore& operator=(const PredictionModelStore&) = delete;
+  ~PredictionModelStore();
+
+  // Initializes the model store with |local_state| and the |base_store_dir|, if
+  // initialization hasn't happened already. Model store will be usable only
+  // after it is initialized.
+
+  // Returns whether the model represented by |optimization_target| and
+  // |model_cache_key| is available in the store.
+  bool HasModel(proto::OptimizationTarget optimization_target,
+                const proto::ModelCacheKey& model_cache_key) const;
+
+  // Loads the model represented by |optimization_target| and
+  // |model_cache_key|. Once the model is loaded and validated |callback|
+  // is invoked. On any failures, callback is run with nullptr.
+  void LoadModel(proto::OptimizationTarget optimization_target,
+                 const proto::ModelCacheKey& model_cache_key,
+                 PredictionModelLoadedCallback callback);
+
+  // Update the model for |model_info| in the store represented by
+  // |optimization_target| and |model_cache_key|. The model files are stored in
+  // |base_model_dir|. |callback| is invoked on completion.
+  void UpdateModel(proto::OptimizationTarget optimization_target,
+                   const proto::ModelCacheKey& model_cache_key,
+                   const proto::ModelInfo& model_info,
+                   const base::FilePath& base_model_dir,
+                   base::OnceClosure callback);
+
+  // Returns the base model dir where the model files, full modelinfo, etc
+  // should be stored, for the model represented by |optimization_target| and
+  // |model_cache_key|.
+  base::FilePath GetBaseModelDirForModelCacheKey(
+      proto::OptimizationTarget optimization_target,
+      const proto::ModelCacheKey& model_cache_key);
+
+ private:
+  friend class PredictionModelStoreTest;
+
+  // Loads the model and verifies if the model files exist and returns the
+  // model. Otherwise nullptr is returned on any failures.
+  static std::unique_ptr<proto::PredictionModel>
+  LoadAndVerifyModelInBackgroundThread(
+      proto::OptimizationTarget optimization_target,
+      const base::FilePath& base_model_dir);
+
+  // Invoked when the model loaded.
+  void OnModelLoaded(proto::OptimizationTarget optimization_target,
+                     const proto::ModelCacheKey& model_cache_key,
+                     PredictionModelLoadedCallback callback,
+                     std::unique_ptr<proto::PredictionModel> model);
+
+  // Invoked when the model files are verified on a model update.
+  void OnModelUpdateVerified(proto::OptimizationTarget optimization_target,
+                             const proto::ModelCacheKey& model_cache_key,
+                             base::OnceClosure callback,
+                             bool model_paths_exist);
+
+  // Local state that stores the prefs across all profiles. Not owned and
+  // outlives |this|.
+  raw_ptr<PrefService> local_state_ GUARDED_BY_CONTEXT(sequence_checker_) =
+      nullptr;
+
+  // The base dir where the prediction model dirs are saved.
+  base::FilePath base_store_dir_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Background thread where file processing should be performed.
+  scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+  base::WeakPtrFactory<PredictionModelStore> weak_ptr_factory_{this};
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_STORE_H_
\ No newline at end of file
diff --git a/components/optimization_guide/core/prediction_model_store_unittest.cc b/components/optimization_guide/core/prediction_model_store_unittest.cc
new file mode 100644
index 0000000..4d2828e
--- /dev/null
+++ b/components/optimization_guide/core/prediction_model_store_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/prediction_model_store.h"
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/guid.h"
+#include "base/rand_util.h"
+#include "base/task/thread_pool.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/optimization_guide/core/model_store_metadata_entry.h"
+#include "components/optimization_guide/core/model_util.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace optimization_guide {
+
+namespace {
+
+const proto::OptimizationTarget kTestOptimizationTargetFoo =
+    proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD;
+const proto::OptimizationTarget kTestOptimizationTargetBar =
+    proto::OptimizationTarget::OPTIMIZATION_TARGET_MODEL_VALIDATION;
+
+const char kTestLocaleFoo[] = "foo";
+
+proto::ModelCacheKey CreateModelCacheKey(const std::string& locale) {
+  proto::ModelCacheKey model_cache_key;
+  model_cache_key.set_locale(locale);
+  return model_cache_key;
+}
+
+struct ModelDetail {
+  proto::ModelInfo model_info;
+  base::FilePath base_model_dir;
+};
+
+}  // namespace
+
+class PredictionModelStoreTest : public testing::Test {
+ public:
+  PredictionModelStoreTest() {
+    feature_list_.InitWithFeatures(
+        {features::kRemoteOptimizationGuideFetching,
+         features::kOptimizationGuideModelDownloading,
+         features::kOptimizationGuideInstallWideModelStore},
+        {});
+  }
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_models_dir_.CreateUniqueTempDir());
+    local_state_prefs_ = std::make_unique<TestingPrefServiceSimple>();
+    prefs::RegisterLocalStatePrefs(local_state_prefs_->registry());
+    prediction_model_store_ = std::make_unique<PredictionModelStore>(
+        local_state_prefs_.get(), temp_models_dir_.GetPath());
+  }
+
+  void OnPredictionModelLoaded(
+      std::unique_ptr<proto::PredictionModel> loaded_prediction_model) {
+    last_loaded_prediction_model_ = std::move(loaded_prediction_model);
+  }
+
+  proto::PredictionModel* last_loaded_prediction_model() {
+    return last_loaded_prediction_model_.get();
+  }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+  // Creates model files and returns model details.
+  ModelDetail CreateTestModelFiles(
+      proto::OptimizationTarget optimization_target,
+      const proto::ModelCacheKey& model_cache_key,
+      const std::vector<const base::FilePath::CharType*>
+          additional_file_names) {
+    auto base_model_dir =
+        prediction_model_store_->GetBaseModelDirForModelCacheKey(
+            optimization_target, model_cache_key);
+    base::CreateDirectory(base_model_dir);
+    base::WriteFile(base_model_dir.Append(GetBaseFileNameForModels()), "");
+    proto::ModelInfo model_info;
+    model_info.set_optimization_target(optimization_target);
+    model_info.set_version(1);
+    for (const auto* additional_file_name : additional_file_names) {
+      base::WriteFile(base_model_dir.Append(additional_file_name), "");
+      model_info.add_additional_files()->set_file_path(
+          FilePathToString(base_model_dir.Append(additional_file_name)));
+    }
+    *model_info.mutable_model_cache_key() = model_cache_key;
+    std::string model_info_pb;
+    model_info.SerializeToString(&model_info_pb);
+    base::WriteFile(base_model_dir.Append(GetBaseFileNameForModelInfo()),
+                    model_info_pb);
+    return {model_info, base_model_dir};
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+  base::test::TaskEnvironment task_environment_;
+  base::ScopedTempDir temp_models_dir_;
+  std::unique_ptr<TestingPrefServiceSimple> local_state_prefs_;
+  std::unique_ptr<proto::PredictionModel> last_loaded_prediction_model_;
+  std::unique_ptr<PredictionModelStore> prediction_model_store_;
+};
+
+TEST_F(PredictionModelStoreTest, BaseModelDirs) {
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+  auto base_model_dir =
+      prediction_model_store_->GetBaseModelDirForModelCacheKey(
+          kTestOptimizationTargetFoo, model_cache_key);
+  EXPECT_TRUE(base_model_dir.IsAbsolute());
+  EXPECT_TRUE(temp_models_dir_.GetPath().IsParent(base_model_dir));
+}
+
+TEST_F(PredictionModelStoreTest, ModelUpdateAndLoad) {
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+
+  EXPECT_FALSE(prediction_model_store_->HasModel(kTestOptimizationTargetFoo,
+                                                 model_cache_key));
+  auto model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetFoo, model_cache_key, {});
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+  EXPECT_TRUE(prediction_model_store_->HasModel(kTestOptimizationTargetFoo,
+                                                model_cache_key));
+  EXPECT_FALSE(prediction_model_store_->HasModel(kTestOptimizationTargetBar,
+                                                 model_cache_key));
+
+  prediction_model_store_->LoadModel(
+      kTestOptimizationTargetFoo, model_cache_key,
+      base::BindOnce(&PredictionModelStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  RunUntilIdle();
+
+  proto::PredictionModel* loaded_model = last_loaded_prediction_model();
+  EXPECT_TRUE(loaded_model);
+  EXPECT_EQ(kTestOptimizationTargetFoo,
+            loaded_model->model_info().optimization_target());
+  EXPECT_EQ(StringToFilePath(loaded_model->model().download_url()).value(),
+            model_detail.base_model_dir.Append(GetBaseFileNameForModels()));
+  EXPECT_EQ(0, loaded_model->model_info().additional_files_size());
+
+  prediction_model_store_->LoadModel(
+      kTestOptimizationTargetBar, model_cache_key,
+      base::BindOnce(&PredictionModelStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  RunUntilIdle();
+  EXPECT_FALSE(last_loaded_prediction_model());
+}
+
+// Tests model with an additional file.
+TEST_F(PredictionModelStoreTest, ModelWithAdditionalFile) {
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+  auto model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetFoo, model_cache_key,
+                           {FILE_PATH_LITERAL("valid_additional_file.txt")});
+
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+  EXPECT_TRUE(prediction_model_store_->HasModel(kTestOptimizationTargetFoo,
+                                                model_cache_key));
+
+  prediction_model_store_->LoadModel(
+      kTestOptimizationTargetFoo, model_cache_key,
+      base::BindOnce(&PredictionModelStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  RunUntilIdle();
+
+  EXPECT_TRUE(last_loaded_prediction_model());
+}
+
+// Tests model with invalid additional file.
+TEST_F(PredictionModelStoreTest, InvalidModelAdditionalFile) {
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+  auto model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetFoo, model_cache_key,
+                           {FILE_PATH_LITERAL("valid_additional_file.txt"),
+                            FILE_PATH_LITERAL("invalid_additional_file.txt")});
+  base::DeleteFile(
+      model_detail.base_model_dir.AppendASCII("invalid_additional_file.txt"));
+
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+  EXPECT_TRUE(prediction_model_store_->HasModel(kTestOptimizationTargetFoo,
+                                                model_cache_key));
+
+  prediction_model_store_->LoadModel(
+      kTestOptimizationTargetFoo, model_cache_key,
+      base::BindOnce(&PredictionModelStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  RunUntilIdle();
+
+  EXPECT_FALSE(last_loaded_prediction_model());
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/optimization_guide_internals/resources/BUILD.gn b/components/optimization_guide/optimization_guide_internals/resources/BUILD.gn
index d9a6e35..7592dc6 100644
--- a/components/optimization_guide/optimization_guide_internals/resources/BUILD.gn
+++ b/components/optimization_guide/optimization_guide_internals/resources/BUILD.gn
@@ -35,7 +35,7 @@
 copy("copy_mojo") {
   deps = [
     "//components/optimization_guide/core:interfaces_js__generator",
-    "//components/optimization_guide/optimization_guide_internals/webui:mojo_bindings_webui_js",
+    "//components/optimization_guide/optimization_guide_internals/webui:mojo_bindings_js__generator",
   ]
   mojom_folder = "$root_gen_dir/mojom-webui/components/optimization_guide/optimization_guide_internals/webui/"
   core_mojom_folder =
diff --git a/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc b/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
index 579a9f4..34d0c54 100644
--- a/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
@@ -198,7 +198,7 @@
     run_loop_->Run();
     run_loop_ = nullptr;
   }
-  EXPECT_TRUE(ExpectationsSatisfied());
+  AssertExpectationsSatisfied();
   ResetExpectations();
 }
 
@@ -549,6 +549,23 @@
          TotalInputDelayExpectationsSatisfied();
 }
 
+void PageLoadMetricsTestWaiter::AssertExpectationsSatisfied() const {
+  EXPECT_TRUE(expected_.page_fields_.AreAllSetIn(observed_.page_fields_));
+  EXPECT_TRUE(
+      expected_.subframe_fields_.AreAllSetIn(observed_.subframe_fields_));
+  EXPECT_TRUE(ResourceUseExpectationsSatisfied());
+  EXPECT_TRUE(UseCounterExpectationsSatisfied());
+  EXPECT_TRUE(SubframeNavigationExpectationsSatisfied());
+  EXPECT_TRUE(SubframeDataExpectationsSatisfied());
+  EXPECT_TRUE(IsSubset(expected_.frame_sizes_, observed_.frame_sizes_));
+  EXPECT_TRUE(LoadingBehaviorExpectationsSatisfied());
+  EXPECT_TRUE(CpuTimeExpectationsSatisfied());
+  EXPECT_TRUE(MainFrameIntersectionExpectationsSatisfied());
+  EXPECT_TRUE(MainFrameViewportRectExpectationsSatisfied());
+  EXPECT_TRUE(MemoryUpdateExpectationsSatisfied());
+  EXPECT_TRUE(TotalInputDelayExpectationsSatisfied());
+}
+
 void PageLoadMetricsTestWaiter::ResetExpectations() {
   expected_ = State();
   observed_ = State();
diff --git a/components/page_load_metrics/browser/page_load_metrics_test_waiter.h b/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
index c7feaa4..5d3dbcca 100644
--- a/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
+++ b/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
@@ -132,6 +132,7 @@
 
  protected:
   virtual bool ExpectationsSatisfied() const;
+  void AssertExpectationsSatisfied() const;
 
   // Intended to be overridden in tests to allow tests to wait on other resource
   // conditions.
diff --git a/components/reporting/metrics/periodic_event_collector.cc b/components/reporting/metrics/periodic_event_collector.cc
index 5ca8175..1a54acc0 100644
--- a/components/reporting/metrics/periodic_event_collector.cc
+++ b/components/reporting/metrics/periodic_event_collector.cc
@@ -45,6 +45,8 @@
 
 void PeriodicEventCollector::SetReportingEnabled(bool is_enabled) {
   if (is_enabled) {
+    // Do initial collection at startup.
+    Collect();
     rate_controller_->Start();
     return;
   }
diff --git a/components/reporting/metrics/periodic_event_collector_unittest.cc b/components/reporting/metrics/periodic_event_collector_unittest.cc
index 4fe12c3..a138d4c 100644
--- a/components/reporting/metrics/periodic_event_collector_unittest.cc
+++ b/components/reporting/metrics/periodic_event_collector_unittest.cc
@@ -45,7 +45,6 @@
       absl::optional<MetricData> previous_metric_data,
       const MetricData& current_metric_data) override {
     previous_metric_data_ = previous_metric_data;
-    run_loop_ptr_->Quit();
     return event_type_;
   }
 
@@ -57,14 +56,10 @@
     return previous_metric_data_;
   }
 
-  void SetRunLoop(base::RunLoop* run_loop_ptr) { run_loop_ptr_ = run_loop_ptr; }
-
  private:
   absl::optional<MetricData> previous_metric_data_;
 
   absl::optional<MetricEventType> event_type_;
-
-  raw_ptr<base::RunLoop> run_loop_ptr_;
 };
 
 class PeriodicEventCollectorTest : public ::testing::Test {
@@ -94,7 +89,7 @@
 
   settings_->SetInteger(kRateSettingPath, interval);
   MetricData sampler_data;
-  sampler_data.mutable_telemetry_data()->mutable_audio_telemetry();
+  sampler_data.mutable_telemetry_data()->mutable_networks_telemetry();
 
   PeriodicEventCollector periodic_event_collector(
       sampler_.get(), std::move(event_detector_), settings_.get(),
@@ -108,6 +103,20 @@
   sampler_->SetMetricData(sampler_data);
 
   {
+    // task_environment_.FastForwardBy(base::Milliseconds(interval));
+    base::RunLoop().RunUntilIdle();
+
+    // Reporting enabled not set, sampler data is not collected and no events
+    // are observed.
+    EXPECT_THAT(sampler_->GetNumCollectCalls(),
+                testing::Eq(expected_collections));
+    EXPECT_FALSE(event_observed_called);
+  }
+
+  {
+    // Setting reporting enabled to initially to false should not cause
+    // event collection.
+    periodic_event_collector.SetReportingEnabled(false);
     task_environment_.FastForwardBy(base::Milliseconds(interval));
     base::RunLoop().RunUntilIdle();
 
@@ -119,7 +128,26 @@
   }
 
   {
+    // Reporting enabled, one initial collection should take place.
     periodic_event_collector.SetReportingEnabled(true);
+    ++expected_collections;
+    base::RunLoop().RunUntilIdle();
+
+    EXPECT_THAT(sampler_->GetNumCollectCalls(), Eq(expected_collections));
+    EXPECT_FALSE(event_detector_ptr_->GetPreviousMetricData().has_value());
+    EXPECT_TRUE(event_observed_called);
+    EXPECT_THAT(event_metric_data.event_data().type(), Eq(event_type));
+    EXPECT_TRUE(event_metric_data.has_timestamp_ms());
+    EXPECT_TRUE(event_metric_data.has_telemetry_data());
+    EXPECT_TRUE(event_metric_data.telemetry_data().has_networks_telemetry());
+  }
+
+  {
+    event_observed_called = false;
+    sampler_data.Clear();
+    sampler_data.mutable_telemetry_data()->mutable_audio_telemetry();
+    sampler_->SetMetricData(sampler_data);
+
     // Only forward time by half of the collection interval.
     task_environment_.FastForwardBy(base::Milliseconds(interval / 2));
     base::RunLoop().RunUntilIdle();
@@ -130,14 +158,19 @@
     EXPECT_FALSE(event_observed_called);
 
     // Forward time by the remaining half of the collection interval.
-    base::RunLoop run_loop;
-    event_detector_ptr_->SetRunLoop(&run_loop);
     task_environment_.FastForwardBy(base::Milliseconds(interval / 2));
-    run_loop.Run();
+    base::RunLoop().RunUntilIdle();
 
     ++expected_collections;
     EXPECT_THAT(sampler_->GetNumCollectCalls(), Eq(expected_collections));
-    EXPECT_FALSE(event_detector_ptr_->GetPreviousMetricData().has_value());
+    // Assert previously collected data.
+    ASSERT_TRUE(event_detector_ptr_->GetPreviousMetricData().has_value());
+    MetricData previous_metric_data =
+        event_detector_ptr_->GetPreviousMetricData().value();
+    EXPECT_THAT(previous_metric_data.event_data().type(), Eq(event_type));
+    EXPECT_TRUE(previous_metric_data.has_telemetry_data());
+    EXPECT_TRUE(previous_metric_data.telemetry_data().has_networks_telemetry());
+    // Assert current collected data.
     EXPECT_TRUE(event_observed_called);
     EXPECT_THAT(event_metric_data.event_data().type(), Eq(event_type));
     EXPECT_TRUE(event_metric_data.has_timestamp_ms());
@@ -153,10 +186,8 @@
     sampler_->SetMetricData(sampler_data);
 
     // Forward time by the the collection interval.
-    base::RunLoop run_loop;
-    event_detector_ptr_->SetRunLoop(&run_loop);
     task_environment_.FastForwardBy(base::Milliseconds(interval));
-    run_loop.Run();
+    base::RunLoop().RunUntilIdle();
 
     ++expected_collections;
     EXPECT_THAT(sampler_->GetNumCollectCalls(), Eq(expected_collections));
diff --git a/components/search_engines/PRESUBMIT.py b/components/search_engines/PRESUBMIT.py
new file mode 100644
index 0000000..053f944
--- /dev/null
+++ b/components/search_engines/PRESUBMIT.py
@@ -0,0 +1,36 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for changes affecting components/search_engines/
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+USE_PYTHON3 = True
+
+import os
+
+def _CheckPrepopulatedEnginesVersion(input_api, output_api):
+  """Check that no changes were made to prepopulated_engines.json without
+  also updating kCurrentDataVersion"""
+  results = []
+  file = next((f for f in input_api.AffectedFiles()
+                 if os.path.basename(f.LocalPath()) ==
+                 "prepopulated_engines.json"), None)
+
+  if file != None and not any(line for line in file.ChangedContents()
+                              if "kCurrentDataVersion" in line[1]):
+      results.append(output_api.PresubmitPromptWarning(
+          ("prepopulated_engines.json changed but kCurrentDataVersion "
+           "did not. Please ensure the version is rolled up when making "
+           "meaningful changes to prepopulated_engines.json")))
+
+  return results
+
+def CheckChangeOnUpload(input_api, output_api):
+  results = []
+  results.extend(_CheckPrepopulatedEnginesVersion(input_api, output_api))
+  return results
+
diff --git a/components/tab_groups/BUILD.gn b/components/tab_groups/BUILD.gn
index f76cb303..98db0304 100644
--- a/components/tab_groups/BUILD.gn
+++ b/components/tab_groups/BUILD.gn
@@ -17,7 +17,7 @@
   deps = [
     "//base",
     "//components/strings",
-    "//components/tab_groups/public/mojom:mojo_bindings_webui_js",
+    "//components/tab_groups/public/mojom:mojo_bindings_js__generator",
     "//skia",
     "//ui/base",
     "//ui/gfx",
diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc
index af4d6679..a44d536 100644
--- a/components/update_client/update_checker.cc
+++ b/components/update_client/update_checker.cc
@@ -191,7 +191,7 @@
         config_->GetLang(), metadata_->GetInstallDate(app_id), install_source,
         crx_component->install_location, crx_component->fingerprint,
         crx_component->installer_attributes, metadata_->GetCohort(app_id),
-        metadata_->GetCohortName(app_id), metadata_->GetCohortHint(app_id),
+        metadata_->GetCohortHint(app_id), metadata_->GetCohortName(app_id),
         crx_component->channel, crx_component->disabled_reasons,
         MakeProtocolUpdateCheck(!crx_component->updates_enabled,
                                 crx_component->target_version_prefix,
diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc
index 4b4638b..f457426 100644
--- a/components/update_client/update_checker_unittest.cc
+++ b/components/update_client/update_checker_unittest.cc
@@ -242,6 +242,10 @@
                                   {"updatepolicy", "1"}};
   }));
 
+  metadata_->SetCohort(kUpdateItemId, "id3");
+  metadata_->SetCohortHint(kUpdateItemId, "hint2");
+  metadata_->SetCohortName(kUpdateItemId, "name1");
+
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
   update_context_->components[kUpdateItemId] =
@@ -320,6 +324,9 @@
   EXPECT_EQ("TEST", *app->FindString("brand"));
   ASSERT_TRUE(app->FindString("lang"));
   EXPECT_EQ("fake_lang", *app->FindString("lang"));
+  EXPECT_EQ("name1", *app->FindString("cohortname"));
+  EXPECT_EQ("hint2", *app->FindString("cohorthint"));
+  EXPECT_EQ("id3", *app->FindString("cohort"));
 
   ASSERT_TRUE(app->FindList("data"));
   ASSERT_FALSE(app->FindList("data")->empty());
diff --git a/components/version_ui/resources/about_version.js b/components/version_ui/resources/about_version.js
index 5d7f315..22db5c6c 100644
--- a/components/version_ui/resources/about_version.js
+++ b/components/version_ui/resources/about_version.js
@@ -11,7 +11,7 @@
 // </if>
 
 import './strings.m.js';
-import {addWebUIListener, sendWithPromise} from 'chrome://resources/js/cr.js';
+import {addWebUiListener, sendWithPromise} from 'chrome://resources/js/cr.js';
 import {$} from 'chrome://resources/js/util.js';
 
 /**
@@ -119,16 +119,16 @@
 /* All the work we do onload. */
 function onLoadWork() {
   // <if expr="chromeos_ash or is_win">
-  addWebUIListener('return-os-version', returnOsVersion);
+  addWebUiListener('return-os-version', returnOsVersion);
   // </if>
   // <if expr="chromeos_ash">
-  addWebUIListener('return-os-firmware-version', returnOsFirmwareVersion);
-  addWebUIListener(
+  addWebUiListener('return-os-firmware-version', returnOsFirmwareVersion);
+  addWebUiListener(
       'return-arc-and-arc-android-sdk-versions',
       returnArcAndArcAndroidSdkVersions);
   // </if>
   // <if expr="is_chromeos">
-  addWebUIListener('return-lacros-primary', returnLacrosPrimary);
+  addWebUiListener('return-lacros-primary', returnLacrosPrimary);
   // </if>
 
   chrome.send('requestVersionInfo');
diff --git a/content/BUILD.gn b/content/BUILD.gn
index 618cb81..c96243b 100644
--- a/content/BUILD.gn
+++ b/content/BUILD.gn
@@ -96,16 +96,16 @@
   deps = [
     "//components/ukm/debug:build_ts",
     "//content/browser/resources/histograms:build_ts",
-    "//gpu/ipc/common:vulkan_interface_webui_js",
-    "//ui/base/accelerators/mojom:mojom_webui_js",
+    "//gpu/ipc/common:vulkan_interface_js__generator",
+    "//ui/base/accelerators/mojom:mojom_js__generator",
     "//ui/base/mojom:mojom_js",
-    "//ui/events/mojom:mojom_webui_js",
+    "//ui/events/mojom:mojom_js__generator",
     "//ui/gfx/geometry/mojom:mojom_js",
     "//ui/gfx/image/mojom:mojom_js",
     "//ui/gfx/range/mojom:mojom_js",
-    "//ui/latency/mojom:mojom_webui_js",
-    "//url/mojom:url_mojom_origin_js",
-    "//url/mojom:url_mojom_origin_webui_js",
+    "//ui/latency/mojom:mojom_js__generator",
+    "//url/mojom:url_mojom_gurl_js__generator",
+    "//url/mojom:url_mojom_origin_js__generator",
   ]
 }
 
@@ -117,15 +117,15 @@
     "dev_ui_content_resources.pak",
   ]
   deps = [
-    "//components/attribution_reporting:mojom_webui_js",
-    "//content/browser/aggregation_service:mojo_bindings_webui_js",
-    "//content/browser/attribution_reporting:internals_mojo_bindings_webui_js",
-    "//content/browser/attribution_reporting:mojo_bindings_webui_js",
+    "//components/attribution_reporting:mojom_js__generator",
+    "//content/browser/aggregation_service:mojo_bindings_js__generator",
+    "//content/browser/attribution_reporting:internals_mojo_bindings_js__generator",
+    "//content/browser/attribution_reporting:mojo_bindings_js__generator",
     "//content/browser/resources/aggregation_service:build_ts",
     "//content/browser/resources/attribution_reporting:build_ts",
     "//content/browser/resources/gpu:html_wrapper_files",
     "//content/browser/resources/process:build_ts",
-    "//storage/browser/quota:mojo_bindings_webui_js",
+    "//storage/browser/quota:mojo_bindings_js__generator",
   ]
 }
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 3f65744..ac6cba6c 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -641,6 +641,10 @@
     "browsing_topics/browsing_topics_site_data_manager_impl.h",
     "browsing_topics/browsing_topics_site_data_storage.cc",
     "browsing_topics/browsing_topics_site_data_storage.h",
+    "browsing_topics/browsing_topics_url_loader.cc",
+    "browsing_topics/browsing_topics_url_loader.h",
+    "browsing_topics/browsing_topics_url_loader_service.cc",
+    "browsing_topics/browsing_topics_url_loader_service.h",
     "browsing_topics/header_util.cc",
     "browsing_topics/header_util.h",
     "buckets/bucket_context.h",
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader.cc b/content/browser/browsing_topics/browsing_topics_url_loader.cc
new file mode 100644
index 0000000..623a4072
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_url_loader.cc
@@ -0,0 +1,241 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/browsing_topics/browsing_topics_url_loader.h"
+
+#include "base/bind.h"
+#include "content/browser/browsing_topics/header_util.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/page.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_features.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/mojom/early_hints.mojom.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/frame/frame_policy.h"
+#include "third_party/blink/public/mojom/browsing_topics/browsing_topics.mojom.h"
+
+namespace content {
+
+namespace {
+
+bool GetTopicsHeaderValueForSubresourceRequest(
+    WeakDocumentPtr request_initiator_document,
+    const GURL& url,
+    std::string& header_value) {
+  DCHECK(header_value.empty());
+
+  // Due to the race between the subresource requests and navigations, this
+  // request may arrive before the commit confirmation is received (i.e.
+  // NavigationRequest::DidCommitNavigation()), or after the document is
+  // destroyed. We consider those cases to be ineligible for topics.
+  //
+  // TODO(yaoxia): measure how often this happens.
+  RenderFrameHost* request_initiator_frame =
+      request_initiator_document.AsRenderFrameHostIfValid();
+  if (!request_initiator_frame)
+    return false;
+
+  // Fenced frames disallow most permissions policies which would let this
+  // function return false regardless, but adding this check to be more
+  // explicit.
+  if (request_initiator_frame->IsNestedWithinFencedFrame())
+    return false;
+
+  if (!request_initiator_frame->GetPage().IsPrimary())
+    return false;
+
+  // TODO(crbug.com/1244137): IsPrimary() doesn't actually detect portals yet.
+  // Remove this when it does.
+  if (!static_cast<RenderFrameHostImpl*>(request_initiator_frame)
+           ->IsOutermostMainFrame()) {
+    return false;
+  }
+
+  url::Origin origin = url::Origin::Create(url);
+  if (origin.opaque())
+    return false;
+
+  // TODO(yaoxia): should this be `ReportBadMessage`? On the renderer side, the
+  // fetch initiator context must be secure. Does it imply that the requested
+  // `origin` is always potentially trustworthy?
+  if (!network::IsOriginPotentiallyTrustworthy(origin))
+    return false;
+
+  const blink::PermissionsPolicy* permissions_policy =
+      static_cast<RenderFrameHostImpl*>(request_initiator_frame)
+          ->permissions_policy();
+
+  if (!permissions_policy->IsFeatureEnabledForOrigin(
+          blink::mojom::PermissionsPolicyFeature::kBrowsingTopics, origin) ||
+      !permissions_policy->IsFeatureEnabledForOrigin(
+          blink::mojom::PermissionsPolicyFeature::
+              kBrowsingTopicsBackwardCompatible,
+          origin)) {
+    return false;
+  }
+
+  std::vector<blink::mojom::EpochTopicPtr> topics;
+  bool topics_eligible = GetContentClient()->browser()->HandleTopicsWebApi(
+      origin, request_initiator_frame->GetMainFrame(),
+      browsing_topics::ApiCallerSource::kFetch,
+      /*get_topics=*/true,
+      /*observe=*/false, topics);
+
+  if (topics_eligible)
+    header_value = DeriveTopicsHeaderValue(topics);
+
+  return topics_eligible;
+}
+
+void ProcessResponseHeaders(const net::HttpResponseHeaders* response_headers,
+                            WeakDocumentPtr document,
+                            const GURL& url) {
+  if (!response_headers)
+    return;
+
+  RenderFrameHost* rfh = document.AsRenderFrameHostIfValid();
+  if (!rfh)
+    return;
+
+  HandleTopicsEligibleResponse(*response_headers, url::Origin::Create(url),
+                               *rfh, browsing_topics::ApiCallerSource::kFetch);
+}
+
+}  // namespace
+
+BrowsingTopicsURLLoader::BrowsingTopicsURLLoader(
+    WeakDocumentPtr document,
+    int32_t request_id,
+    uint32_t options,
+    const network::ResourceRequest& resource_request,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+    scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory)
+    : document_(std::move(document)),
+      url_(resource_request.url),
+      forwarding_client_(std::move(client)) {
+  DCHECK(network_loader_factory);
+
+  network::ResourceRequest new_resource_request = resource_request;
+
+  std::string header_value;
+  topics_eligible_ = GetTopicsHeaderValueForSubresourceRequest(
+      document_, new_resource_request.url, header_value);
+
+  if (topics_eligible_) {
+    new_resource_request.headers.SetHeader(kBrowsingTopicsRequestHeaderKey,
+                                           header_value);
+  }
+
+  network_loader_factory->CreateLoaderAndStart(
+      loader_.BindNewPipeAndPassReceiver(), request_id, options,
+      new_resource_request, client_receiver_.BindNewPipeAndPassRemote(),
+      traffic_annotation);
+
+  client_receiver_.set_disconnect_handler(
+      base::BindOnce(&BrowsingTopicsURLLoader::OnNetworkConnectionError,
+                     base::Unretained(this)));
+}
+
+BrowsingTopicsURLLoader::~BrowsingTopicsURLLoader() = default;
+
+void BrowsingTopicsURLLoader::FollowRedirect(
+    const std::vector<std::string>& removed_headers,
+    const net::HttpRequestHeaders& modified_headers,
+    const net::HttpRequestHeaders& modified_cors_exempt_headers,
+    const absl::optional<GURL>& new_url) {
+  if (new_url)
+    url_ = new_url.value();
+
+  std::vector<std::string> new_removed_headers = removed_headers;
+  net::HttpRequestHeaders new_modified_headers = modified_headers;
+
+  new_removed_headers.push_back(kBrowsingTopicsRequestHeaderKey);
+
+  std::string header_value;
+  topics_eligible_ =
+      GetTopicsHeaderValueForSubresourceRequest(document_, url_, header_value);
+
+  if (topics_eligible_) {
+    new_modified_headers.SetHeader(kBrowsingTopicsRequestHeaderKey,
+                                   header_value);
+  }
+
+  loader_->FollowRedirect(new_removed_headers, new_modified_headers,
+                          modified_cors_exempt_headers, new_url);
+}
+
+void BrowsingTopicsURLLoader::SetPriority(net::RequestPriority priority,
+                                          int intra_priority_value) {
+  loader_->SetPriority(priority, intra_priority_value);
+}
+
+void BrowsingTopicsURLLoader::PauseReadingBodyFromNet() {
+  loader_->PauseReadingBodyFromNet();
+}
+
+void BrowsingTopicsURLLoader::ResumeReadingBodyFromNet() {
+  loader_->ResumeReadingBodyFromNet();
+}
+
+void BrowsingTopicsURLLoader::OnReceiveEarlyHints(
+    network::mojom::EarlyHintsPtr early_hints) {
+  forwarding_client_->OnReceiveEarlyHints(std::move(early_hints));
+}
+
+void BrowsingTopicsURLLoader::OnReceiveResponse(
+    network::mojom::URLResponseHeadPtr head,
+    mojo::ScopedDataPipeConsumerHandle body,
+    absl::optional<mojo_base::BigBuffer> cached_metadata) {
+  if (topics_eligible_) {
+    ProcessResponseHeaders(head->headers.get(), document_, url_);
+    topics_eligible_ = false;
+  }
+
+  forwarding_client_->OnReceiveResponse(std::move(head), std::move(body),
+                                        std::move(cached_metadata));
+}
+
+void BrowsingTopicsURLLoader::OnReceiveRedirect(
+    const net::RedirectInfo& redirect_info,
+    network::mojom::URLResponseHeadPtr head) {
+  if (topics_eligible_) {
+    ProcessResponseHeaders(head->headers.get(), document_, url_);
+    topics_eligible_ = false;
+  }
+
+  url_ = redirect_info.new_url;
+
+  forwarding_client_->OnReceiveRedirect(redirect_info, std::move(head));
+}
+
+void BrowsingTopicsURLLoader::OnUploadProgress(
+    int64_t current_position,
+    int64_t total_size,
+    base::OnceCallback<void()> callback) {
+  forwarding_client_->OnUploadProgress(current_position, total_size,
+                                       std::move(callback));
+}
+
+void BrowsingTopicsURLLoader::OnTransferSizeUpdated(
+    int32_t transfer_size_diff) {
+  forwarding_client_->OnTransferSizeUpdated(transfer_size_diff);
+}
+
+void BrowsingTopicsURLLoader::OnComplete(
+    const network::URLLoaderCompletionStatus& status) {
+  forwarding_client_->OnComplete(status);
+}
+
+void BrowsingTopicsURLLoader::OnNetworkConnectionError() {
+  // The network loader has an error; we should let the client know it's closed
+  // by dropping this, which will in turn make this loader destroyed.
+  forwarding_client_.reset();
+}
+
+}  // namespace content
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader.h b/content/browser/browsing_topics/browsing_topics_url_loader.h
new file mode 100644
index 0000000..8576e64
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_url_loader.h
@@ -0,0 +1,119 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_
+#define CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/weak_document_ptr.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace content {
+
+// A URLLoader for handling a topics request, including
+// fetch(<url>, {browsingTopics: true}).
+//
+// This loader works as follows:
+//   1. Before making a network request (i.e. BrowsingTopicsURLLoader()), if the
+//      request is eligible for topics, calculates and adds the topics header.
+//      Starts the request with `loader_`.
+//   2. For any redirect received (i.e. OnReceiveRedirect()), if the previous
+//      request or redirect was eligible for topics, and if the response header
+//      indicates an observation should be recorded, stores the observation.
+//      Forwards the original response back to `forwarding_client_`.
+//   3. For any followed redirect (i.e. FollowRedirect()),  if the redirect is
+//      eligible for topics, calculates and adds/updates the topics header.
+//      Forwards the updated redirect to `loader_`.
+//   4. For the last response (i.e. OnReceiveResponse()),  if the previous
+//      request or redirect was eligible for topics, and if the response header
+//      indicates an observation should be recorded, stores the observation.
+//      Forwards the original response (e.g. hands off fetching the body) back
+//      to `forwarding_client_`.
+class CONTENT_EXPORT BrowsingTopicsURLLoader
+    : public network::mojom::URLLoader,
+      public network::mojom::URLLoaderClient {
+ public:
+  BrowsingTopicsURLLoader(
+      WeakDocumentPtr document,
+      int32_t request_id,
+      uint32_t options,
+      const network::ResourceRequest& resource_request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+      scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory);
+
+  BrowsingTopicsURLLoader(const BrowsingTopicsURLLoader&) = delete;
+  BrowsingTopicsURLLoader& operator=(const BrowsingTopicsURLLoader&) = delete;
+
+  ~BrowsingTopicsURLLoader() override;
+
+ private:
+  // network::mojom::URLLoader overrides:
+  void FollowRedirect(
+      const std::vector<std::string>& removed_headers,
+      const net::HttpRequestHeaders& modified_headers,
+      const net::HttpRequestHeaders& modified_cors_exempt_headers,
+      const absl::optional<GURL>& new_url) override;
+  void SetPriority(net::RequestPriority priority,
+                   int intra_priority_value) override;
+  void PauseReadingBodyFromNet() override;
+  void ResumeReadingBodyFromNet() override;
+
+  // network::mojom::URLLoaderClient overrides:
+  void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
+  void OnReceiveResponse(
+      network::mojom::URLResponseHeadPtr head,
+      mojo::ScopedDataPipeConsumerHandle body,
+      absl::optional<mojo_base::BigBuffer> cached_metadata) override;
+  void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
+                         network::mojom::URLResponseHeadPtr head) override;
+  void OnUploadProgress(int64_t current_position,
+                        int64_t total_size,
+                        base::OnceCallback<void()> callback) override;
+  void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
+  void OnComplete(const network::URLLoaderCompletionStatus& status) override;
+
+  void OnNetworkConnectionError();
+
+  // Upon NavigationRequest::DidCommitNavigation(), `document_` will be set to
+  // the document that this `BrowsingTopicsURLLoader` is associated with. It
+  // will become null whenever the document navigates away.
+  WeakDocumentPtr document_;
+
+  // The current request or redirect URL.
+  GURL url_;
+
+  // Whether the ongoing request or redirect is eligible for topics. Set to the
+  // desired state when a request/redirect is made. Reset to false when the
+  // corresponding response is received.
+  bool topics_eligible_ = false;
+
+  // For the actual request.
+  mojo::Remote<network::mojom::URLLoader> loader_;
+
+  // The client to forward the response to.
+  mojo::Remote<network::mojom::URLLoaderClient> forwarding_client_;
+
+  mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_H_
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader_service.cc b/content/browser/browsing_topics/browsing_topics_url_loader_service.cc
new file mode 100644
index 0000000..79e3ef8
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_url_loader_service.cc
@@ -0,0 +1,86 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/browsing_topics/browsing_topics_url_loader_service.h"
+
+#include "base/bind.h"
+#include "content/browser/browsing_topics/browsing_topics_url_loader.h"
+#include "content/public/browser/content_browser_client.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+
+namespace content {
+
+BrowsingTopicsURLLoaderService::BindContext::BindContext(
+    scoped_refptr<network::SharedURLLoaderFactory> factory)
+    : factory(factory) {}
+
+BrowsingTopicsURLLoaderService::BindContext::BindContext(
+    const std::unique_ptr<BindContext>& other)
+    : document(other->document), factory(other->factory) {}
+
+void BrowsingTopicsURLLoaderService::BindContext::OnDidCommitNavigation(
+    WeakDocumentPtr committed_document) {
+  document = committed_document;
+}
+
+BrowsingTopicsURLLoaderService::BindContext::~BindContext() = default;
+
+BrowsingTopicsURLLoaderService::BrowsingTopicsURLLoaderService() = default;
+
+BrowsingTopicsURLLoaderService::~BrowsingTopicsURLLoaderService() = default;
+
+base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext>
+BrowsingTopicsURLLoaderService::GetFactory(
+    mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+    std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  auto factory_bundle =
+      network::SharedURLLoaderFactory::Create(std::move(pending_factory));
+
+  auto bind_context = std::make_unique<BindContext>(factory_bundle);
+
+  base::WeakPtr<BindContext> weak_bind_context =
+      bind_context->weak_ptr_factory.GetWeakPtr();
+
+  loader_factory_receivers_.Add(this, std::move(receiver),
+                                std::move(bind_context));
+
+  return weak_bind_context;
+}
+
+void BrowsingTopicsURLLoaderService::CreateLoaderAndStart(
+    mojo::PendingReceiver<network::mojom::URLLoader> receiver,
+    int32_t request_id,
+    uint32_t options,
+    const network::ResourceRequest& resource_request,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  const std::unique_ptr<BindContext>& current_context =
+      loader_factory_receivers_.current_context();
+
+  auto loader = std::make_unique<BrowsingTopicsURLLoader>(
+      current_context->document, request_id, options, resource_request,
+      std::move(client), traffic_annotation, current_context->factory);
+
+  auto* raw_loader = loader.get();
+
+  loader_receivers_.Add(raw_loader, std::move(receiver), std::move(loader));
+}
+
+void BrowsingTopicsURLLoaderService::Clone(
+    mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  loader_factory_receivers_.Add(
+      this, std::move(receiver),
+      std::make_unique<BindContext>(
+          loader_factory_receivers_.current_context()));
+}
+
+}  // namespace content
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader_service.h b/content/browser/browsing_topics/browsing_topics_url_loader_service.h
new file mode 100644
index 0000000..64fefd77
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_url_loader_service.h
@@ -0,0 +1,95 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_SERVICE_H_
+#define CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_SERVICE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/weak_document_ptr.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "third_party/blink/public/common/loader/url_loader_factory_bundle.h"
+
+namespace content {
+
+// A URLLoaderFactory that can be passed to a renderer to use for performing
+// topics related requests. The renderer uses this to handle
+// fetch(<url>, {browsingTopics: true}).
+class CONTENT_EXPORT BrowsingTopicsURLLoaderService final
+    : public network::mojom::URLLoaderFactory {
+ public:
+  struct CONTENT_EXPORT BindContext {
+    // `factory` is a clone of the default factory bundle for document
+    // subresource requests.
+    explicit BindContext(
+        scoped_refptr<network::SharedURLLoaderFactory> factory);
+
+    explicit BindContext(const std::unique_ptr<BindContext>& other);
+
+    ~BindContext();
+
+    // Set `document` to `committed_document`.
+    void OnDidCommitNavigation(WeakDocumentPtr committed_document);
+
+    // Upon NavigationRequest::DidCommitNavigation(), `document` will be set to
+    // the document that this `BindContext` is associated with. It will become
+    // null whenever the document navigates away.
+    WeakDocumentPtr document;
+
+    // The factory to use for the requests initiated from this context.
+    scoped_refptr<network::SharedURLLoaderFactory> factory;
+
+    // This must be the last member.
+    base::WeakPtrFactory<BrowsingTopicsURLLoaderService::BindContext>
+        weak_ptr_factory{this};
+  };
+
+  BrowsingTopicsURLLoaderService();
+
+  ~BrowsingTopicsURLLoaderService() override;
+
+  BrowsingTopicsURLLoaderService(const BrowsingTopicsURLLoaderService&) =
+      delete;
+  BrowsingTopicsURLLoaderService& operator=(
+      const BrowsingTopicsURLLoaderService&) = delete;
+
+  // Binds `receiver`. Creates a `BindContext` to contain a factory constructed
+  // with `pending_factory`, and associates it to `receiver`. Returns the
+  // associated `BindContext`.
+  base::WeakPtr<BindContext> GetFactory(
+      mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+      std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory);
+
+ private:
+  // network::mojom::URLLoaderFactory:
+  void CreateLoaderAndStart(
+      mojo::PendingReceiver<network::mojom::URLLoader> receiver,
+      int32_t request_id,
+      uint32_t options,
+      const network::ResourceRequest& resource_request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+      override;
+  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
+      override;
+
+  mojo::ReceiverSet<network::mojom::URLLoaderFactory,
+                    std::unique_ptr<BindContext>>
+      loader_factory_receivers_;
+
+  mojo::ReceiverSet<network::mojom::URLLoader,
+                    std::unique_ptr<network::mojom::URLLoader>>
+      loader_receivers_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_URL_LOADER_SERVICE_H_
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc b/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc
new file mode 100644
index 0000000..11ebaf46
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc
@@ -0,0 +1,920 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/browsing_topics/browsing_topics_url_loader_service.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_utils.h"
+#include "content/public/test/web_contents_tester.h"
+#include "content/test/test_render_view_host.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "third_party/blink/public/mojom/browsing_topics/browsing_topics.mojom.h"
+
+namespace content {
+
+namespace {
+
+using FollowRedirectParams =
+    network::TestURLLoaderFactory::TestURLLoader::FollowRedirectParams;
+
+constexpr char kExpectedHeaderForOrigin1[] =
+    "1;version=\"chrome.1:1:2\";config_version=\"chrome.1\";model_version="
+    "\"2\";taxonomy_version=\"1\"";
+
+constexpr char kExpectedHeaderForOrigin2[] =
+    "2;version=\"chrome.3:4:5\";config_version=\"chrome.3\";model_version="
+    "\"5\";taxonomy_version=\"4\"";
+
+class TopicsInterceptingContentBrowserClient : public ContentBrowserClient {
+ public:
+  bool HandleTopicsWebApi(
+      const url::Origin& context_origin,
+      content::RenderFrameHost* main_frame,
+      browsing_topics::ApiCallerSource caller_source,
+      bool get_topics,
+      bool observe,
+      std::vector<blink::mojom::EpochTopicPtr>& topics) override {
+    ++handle_topics_web_api_count_;
+    last_get_topics_param_ = get_topics;
+    last_observe_param_ = observe;
+
+    if (get_topics) {
+      if (context_origin == url::Origin::Create(GURL("https://foo1.com"))) {
+        blink::mojom::EpochTopicPtr result_topic =
+            blink::mojom::EpochTopic::New();
+        result_topic->topic = 1;
+        result_topic->config_version = "chrome.1";
+        result_topic->taxonomy_version = "1";
+        result_topic->model_version = "2";
+        result_topic->version = "chrome.1:1:2";
+        topics.push_back(std::move(result_topic));
+      } else if (context_origin ==
+                 url::Origin::Create(GURL("https://foo2.com"))) {
+        blink::mojom::EpochTopicPtr result_topic =
+            blink::mojom::EpochTopic::New();
+        result_topic->topic = 2;
+        result_topic->config_version = "chrome.3";
+        result_topic->taxonomy_version = "4";
+        result_topic->model_version = "5";
+        result_topic->version = "chrome.3:4:5";
+        topics.push_back(std::move(result_topic));
+      }
+    }
+
+    return true;
+  }
+
+  size_t handle_topics_web_api_count() const {
+    return handle_topics_web_api_count_;
+  }
+
+  bool last_get_topics_param() const { return last_get_topics_param_; }
+
+  bool last_observe_param() const { return last_observe_param_; }
+
+ private:
+  size_t handle_topics_web_api_count_ = 0;
+  bool last_get_topics_param_ = false;
+  bool last_observe_param_ = false;
+};
+
+}  // namespace
+
+class BrowsingTopicsURLLoaderServiceTest : public RenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    content::RenderViewHostTestHarness::SetUp();
+
+    original_client_ = content::SetBrowserClientForTesting(&browser_client_);
+  }
+
+  void TearDown() override {
+    SetBrowserClientForTesting(original_client_);
+
+    content::RenderViewHostTestHarness::TearDown();
+  }
+
+  const TopicsInterceptingContentBrowserClient& browser_client() const {
+    return browser_client_;
+  }
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> CreateFactory(
+      network::TestURLLoaderFactory& proxied_url_loader_factory,
+      mojo::Remote<network::mojom::URLLoaderFactory>&
+          remote_url_loader_factory) {
+    if (!browsing_topics_url_loader_service_) {
+      browsing_topics_url_loader_service_ =
+          std::make_unique<BrowsingTopicsURLLoaderService>();
+    }
+
+    mojo::Remote<network::mojom::URLLoaderFactory> factory;
+    proxied_url_loader_factory.Clone(factory.BindNewPipeAndPassReceiver());
+    auto pending_factory =
+        std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
+            factory.Unbind());
+
+    return browsing_topics_url_loader_service_->GetFactory(
+        remote_url_loader_factory.BindNewPipeAndPassReceiver(),
+        std::move(pending_factory));
+  }
+
+  network::mojom::URLResponseHeadPtr CreateResponseHead(
+      absl::optional<std::string> topics_header_value) {
+    auto head = network::mojom::URLResponseHead::New();
+    head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
+
+    if (topics_header_value) {
+      head->headers->AddHeader("Observe-Browsing-Topics",
+                               topics_header_value.value());
+    }
+
+    return head;
+  }
+
+  network::ResourceRequest CreateResourceRequest(const GURL& url) {
+    network::ResourceRequest request;
+    request.url = url;
+    return request;
+  }
+
+  void NavigatePage(const GURL& url) {
+    auto simulator =
+        NavigationSimulator::CreateBrowserInitiated(url, web_contents());
+
+    blink::ParsedPermissionsPolicy policy;
+    policy.emplace_back(blink::mojom::PermissionsPolicyFeature::kBrowsingTopics,
+                        /*allowed_origins=*/
+                        std::vector<blink::OriginWithPossibleWildcards>{
+                            blink::OriginWithPossibleWildcards(
+                                url::Origin::Create(GURL("https://foo1.com")),
+                                /*has_subdomain_wildcard=*/false),
+                            blink::OriginWithPossibleWildcards(
+                                url::Origin::Create(GURL("https://foo2.com")),
+                                /*has_subdomain_wildcard=*/false),
+                            blink::OriginWithPossibleWildcards(
+                                url::Origin::Create(GURL("https://foo3.com")),
+                                /*has_subdomain_wildcard=*/false)},
+                        /*matches_all_origins=*/false,
+                        /*matches_opaque_src=*/false);
+
+    simulator->SetPermissionsPolicyHeader(std::move(policy));
+
+    simulator->Commit();
+  }
+
+ private:
+  TopicsInterceptingContentBrowserClient browser_client_;
+  raw_ptr<ContentBrowserClient> original_client_ = nullptr;
+
+  std::unique_ptr<BrowsingTopicsURLLoaderService>
+      browsing_topics_url_loader_service_;
+};
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, RequestArrivedBeforeCommit) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+
+  // This request arrives before commit. It is thus not eligible for topics.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_FALSE(has_topics_header);
+
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 0u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, RequestArrivedAfterCommit) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // The request to `foo1.com` will cause the topics header value
+  // `kExpectedHeaderForOrigin1` to be added.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // The topics response header value "?1" will cause an observation to be
+  // recorded.
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest,
+       RequestArrivedAfterDocumentDestroyed) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // This second navigation will cause the initial document referenced by the
+  // factory to be destroyed. Thus the request won't be eligible for topics.
+  auto simulator = NavigationSimulator::CreateBrowserInitiated(
+      GURL("https://foo1.com"), web_contents());
+  simulator->Commit();
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_FALSE(has_topics_header);
+
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 0u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, HasNoObserveResponseHeader) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // Expect no further handling for topics as the response does not contain the
+  // topics header.
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/absl::nullopt),
+      /*body=*/{}, absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, HasFalseValueObserveResponseHeader) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // Expect no further handling for topics as the response header value is "?0"
+  // (i.e. false).
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?0"),
+      /*body=*/{}, absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, HasMalformedObserveResponseHeader) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // Expect no further handling for topics as the response header value is
+  // malformed.
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1, ?0"),
+      /*body=*/{}, absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, EmptyTopics) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // The request to `foo3.com` will cause an empty topics header value to be
+  // added.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo3.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_TRUE(topics_header_value.empty());
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // The topics response header value "?1" will cause an observation to be
+  // recorded.
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest,
+       TopicsNotEligibleDueToInactiveFrame) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // Switch the frame to an inactive state. The request won't be eligible for
+  // topics.
+  RenderFrameHostImpl& rfh =
+      static_cast<RenderFrameHostImpl&>(*web_contents()->GetPrimaryMainFrame());
+  rfh.SetLifecycleState(
+      RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted);
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_FALSE(has_topics_header);
+
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 0u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest,
+       TopicsNotEligibleDueToPermissionsPolicy) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // The permissions policy disallows `foo4.com`. The request won't be eligible
+  // for topics.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo4.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_FALSE(has_topics_header);
+
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 0u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, RedirectTopicsUpdated) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory(
+      /*observe_loader_requests=*/true);
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // The request to `foo1.com` will cause the topics header value
+  // `kExpectedHeaderForOrigin1` to be added.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // Redirect to `foo2.com` will cause the topics header to be updated to
+  // `kExpectedHeaderForOrigin2` for the redirect request.
+  net::RedirectInfo redirect_info;
+  redirect_info.new_url = GURL("https://foo2.com");
+
+  // The topics response header value "?1" for the initial request will cause an
+  // observation to be recorded.
+  pending_request->client->OnReceiveRedirect(
+      redirect_info, CreateResponseHead(/*topics_header_value=*/"?1"));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+
+  remote_loader->FollowRedirect(/*removed_headers=*/{},
+                                /*modified_headers=*/{},
+                                /*modified_cors_exempt_headers=*/{},
+                                /*new_url=*/absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  const std::vector<FollowRedirectParams>& follow_redirect_params =
+      pending_request->test_url_loader->follow_redirect_params();
+  EXPECT_EQ(follow_redirect_params.size(), 1u);
+  EXPECT_EQ(follow_redirect_params[0].removed_headers.size(), 1u);
+  EXPECT_EQ(follow_redirect_params[0].removed_headers[0],
+            "Sec-Browsing-Topics");
+
+  std::string redirect_topics_header_value;
+  bool redirect_has_topics_header =
+      follow_redirect_params[0].modified_headers.GetHeader(
+          "Sec-Browsing-Topics", &redirect_topics_header_value);
+  EXPECT_TRUE(redirect_has_topics_header);
+  EXPECT_EQ(redirect_topics_header_value, kExpectedHeaderForOrigin2);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 3u);
+
+  // The topics response header value "?1" will cause an observation to be
+  // recorded.
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 4u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, RedirectNotEligibleForTopics) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory(
+      /*observe_loader_requests=*/true);
+  mojo::Remote<network::mojom::URLLoader> remote_loader;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  // The request to `foo1.com` will cause the topics header value
+  // `kExpectedHeaderForOrigin1` to be added.
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  std::string topics_header_value;
+  bool has_topics_header = pending_request->request.headers.GetHeader(
+      "Sec-Browsing-Topics", &topics_header_value);
+  EXPECT_TRUE(has_topics_header);
+  EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  // The permissions policy disallows `foo4.com`. The redirect is thus not
+  // eligible for topics.
+  net::RedirectInfo redirect_info;
+  redirect_info.new_url = GURL("https://foo4.com");
+
+  // The topics response header value "?1" for the initial request will cause an
+  // observation to be recorded.
+  pending_request->client->OnReceiveRedirect(
+      redirect_info, CreateResponseHead(/*topics_header_value=*/"?1"));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+
+  remote_loader->FollowRedirect(/*removed_headers=*/{},
+                                /*modified_headers=*/{},
+                                /*modified_cors_exempt_headers=*/{},
+                                /*new_url=*/absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  const std::vector<FollowRedirectParams>& follow_redirect_params =
+      pending_request->test_url_loader->follow_redirect_params();
+  EXPECT_EQ(follow_redirect_params.size(), 1u);
+  EXPECT_EQ(follow_redirect_params[0].removed_headers.size(), 1u);
+  EXPECT_EQ(follow_redirect_params[0].removed_headers[0],
+            "Sec-Browsing-Topics");
+
+  std::string redirect_topics_header_value;
+  bool redirect_has_topics_header =
+      follow_redirect_params[0].modified_headers.GetHeader(
+          "Sec-Browsing-Topics", &redirect_topics_header_value);
+  EXPECT_FALSE(redirect_has_topics_header);
+
+  pending_request->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, TwoRequests) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+  network::TestURLLoaderFactory proxied_url_loader_factory;
+
+  mojo::Remote<network::mojom::URLLoader> remote_loader1;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client1;
+
+  mojo::Remote<network::mojom::URLLoader> remote_loader2;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client2;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context =
+      CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+  bind_context->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader1.BindNewPipeAndPassReceiver(),
+      /*request_id=*/1, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client1.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request1 =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  {
+    std::string topics_header_value;
+    bool has_topics_header = pending_request1->request.headers.GetHeader(
+        "Sec-Browsing-Topics", &topics_header_value);
+    EXPECT_TRUE(has_topics_header);
+    EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+    EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+  }
+
+  pending_request1->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+
+  remote_url_loader_factory->CreateLoaderAndStart(
+      remote_loader2.BindNewPipeAndPassReceiver(),
+      /*request_id=*/2, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo4.com")),
+      client2.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory.FlushForTesting();
+
+  EXPECT_EQ(2, proxied_url_loader_factory.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request2 =
+      &proxied_url_loader_factory.pending_requests()->back();
+
+  EXPECT_TRUE(remote_url_loader_factory.is_connected());
+
+  {
+    std::string topics_header_value;
+    bool has_topics_header = pending_request2->request.headers.GetHeader(
+        "Sec-Browsing-Topics", &topics_header_value);
+
+    // Topics not eligible due to permissions policy.
+    EXPECT_FALSE(has_topics_header);
+
+    EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  }
+
+  pending_request2->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, TwoFactories) {
+  NavigatePage(GURL("https://google.com"));
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory1;
+  network::TestURLLoaderFactory proxied_url_loader_factory1;
+  mojo::Remote<network::mojom::URLLoader> remote_loader1;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client1;
+
+  mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory2;
+  network::TestURLLoaderFactory proxied_url_loader_factory2;
+  mojo::Remote<network::mojom::URLLoader> remote_loader2;
+  mojo::PendingReceiver<network::mojom::URLLoaderClient> client2;
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context1 =
+      CreateFactory(proxied_url_loader_factory1, remote_url_loader_factory1);
+  bind_context1->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context2 =
+      CreateFactory(proxied_url_loader_factory2, remote_url_loader_factory2);
+  bind_context2->OnDidCommitNavigation(
+      web_contents()->GetPrimaryMainFrame()->GetWeakDocumentPtr());
+
+  remote_url_loader_factory1->CreateLoaderAndStart(
+      remote_loader1.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo1.com")),
+      client1.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory1.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory1.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request1 =
+      &proxied_url_loader_factory1.pending_requests()->back();
+
+  {
+    std::string topics_header_value;
+    bool has_topics_header = pending_request1->request.headers.GetHeader(
+        "Sec-Browsing-Topics", &topics_header_value);
+    EXPECT_TRUE(has_topics_header);
+    EXPECT_EQ(topics_header_value, kExpectedHeaderForOrigin1);
+
+    EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+  }
+
+  remote_url_loader_factory2->CreateLoaderAndStart(
+      remote_loader2.BindNewPipeAndPassReceiver(),
+      /*request_id=*/0, /*options=*/0,
+      CreateResourceRequest(GURL("https://foo4.com")),
+      client2.InitWithNewPipeAndPassRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  remote_url_loader_factory2.FlushForTesting();
+
+  EXPECT_EQ(1, proxied_url_loader_factory2.NumPending());
+  network::TestURLLoaderFactory::PendingRequest* pending_request2 =
+      &proxied_url_loader_factory2.pending_requests()->back();
+
+  {
+    std::string topics_header_value;
+    bool has_topics_header = pending_request2->request.headers.GetHeader(
+        "Sec-Browsing-Topics", &topics_header_value);
+
+    // Topics not eligible due to permissions policy.
+    EXPECT_FALSE(has_topics_header);
+
+    EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+  }
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 1u);
+
+  pending_request1->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+  EXPECT_FALSE(browser_client().last_get_topics_param());
+  EXPECT_TRUE(browser_client().last_observe_param());
+
+  pending_request2->client->OnReceiveResponse(
+      CreateResponseHead(/*topics_header_value=*/"?1"), /*body=*/{},
+      absl::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(browser_client().handle_topics_web_api_count(), 2u);
+}
+
+TEST_F(BrowsingTopicsURLLoaderServiceTest, BindContextClearedDueToDisconnect) {
+  NavigatePage(GURL("https://google.com"));
+
+  base::WeakPtr<BrowsingTopicsURLLoaderService::BindContext> bind_context;
+
+  {
+    mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory;
+    network::TestURLLoaderFactory proxied_url_loader_factory;
+
+    bind_context =
+        CreateFactory(proxied_url_loader_factory, remote_url_loader_factory);
+
+    EXPECT_TRUE(bind_context);
+  }
+
+  // Destroying `remote_url_loader_factory` would reset `bind_context`.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(bind_context);
+}
+
+}  // namespace content
diff --git a/content/browser/file_system_access/file_system_access_directory_handle_impl.cc b/content/browser/file_system_access/file_system_access_directory_handle_impl.cc
index 83b334e..8f5c3d4 100644
--- a/content/browser/file_system_access/file_system_access_directory_handle_impl.cc
+++ b/content/browser/file_system_access/file_system_access_directory_handle_impl.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/file_system_access/file_system_access_directory_handle_impl.h"
 
+#include "base/feature_list.h"
 #include "base/guid.h"
 #include "base/i18n/file_util_icu.h"
 #include "base/memory/ref_counted_delete_on_sequence.h"
@@ -41,6 +42,12 @@
 using blink::mojom::FileSystemAccessTransferToken;
 using storage::FileSystemOperationRunner;
 
+namespace features {
+BASE_FEATURE(kFileSystemAccessRemoveEntryExclusiveLock,
+             "FileSystemAccessRemoveEntryExclusiveLock",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+}  // namespace features
+
 namespace content {
 
 using HandleType = FileSystemAccessPermissionContext::HandleType;
@@ -384,15 +391,13 @@
     return;
   }
 
-  // TODO(crbug.com/1254078): Consider requiring an exclusive lock to match the
-  // behavior of `remove()`.
-  //
-  // Use a shared write lock to allow the file to be removed if it has an open
-  // writable, but not if it has an open access handle.
+  auto lock_type = base::FeatureList::IsEnabled(
+                       features::kFileSystemAccessRemoveEntryExclusiveLock)
+                       ? WriteLockType::kExclusive
+                       : WriteLockType::kShared;
   RunWithWritePermission(
       base::BindOnce(&FileSystemAccessHandleBase::DoRemove,
-                     weak_factory_.GetWeakPtr(), child_url, recurse,
-                     WriteLockType::kShared),
+                     weak_factory_.GetWeakPtr(), child_url, recurse, lock_type),
       base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result,
                         RemoveEntryCallback callback) {
         std::move(callback).Run(std::move(result));
diff --git a/content/browser/file_system_access/file_system_access_directory_handle_impl.h b/content/browser/file_system_access/file_system_access_directory_handle_impl.h
index fbbd15e8..de8a9403 100644
--- a/content/browser/file_system_access/file_system_access_directory_handle_impl.h
+++ b/content/browser/file_system_access/file_system_access_directory_handle_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_DIRECTORY_HANDLE_IMPL_H_
 #define CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_DIRECTORY_HANDLE_IMPL_H_
 
+#include "base/feature_list.h"
 #include "base/files/file.h"
 #include "base/memory/weak_ptr.h"
 #include "base/thread_annotations.h"
@@ -15,6 +16,13 @@
 #include "storage/browser/file_system/file_system_url.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_directory_handle.mojom.h"
 
+namespace features {
+// TODO(crbug.com/1254078): Remove this flag eventually.
+// When enabled, removeEntry() acquires an exclusive lock (as opposed to a
+// shared lock when disabled).
+BASE_DECLARE_FEATURE(kFileSystemAccessRemoveEntryExclusiveLock);
+}  // namespace features
+
 namespace content {
 
 // This is the browser side implementation of the
diff --git a/content/browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc b/content/browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc
index 4d45ae18..836dfd18 100644
--- a/content/browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc
@@ -399,7 +399,7 @@
   }
 
   // Acquire a shared lock on a file before removing to simulate when the file
-  // has an open writable.
+  // has an open writable. This should also fail.
   {
     base::CreateTemporaryFileInDir(dir, &file);
     auto base_name = storage::FilePathToString(file.BaseName());
@@ -412,8 +412,10 @@
     base::test::TestFuture<blink::mojom::FileSystemAccessErrorPtr> future;
     handle->RemoveEntry(base_name,
                         /*recurse=*/false, future.GetCallback());
-    EXPECT_EQ(future.Get()->status, blink::mojom::FileSystemAccessStatus::kOk);
-    EXPECT_FALSE(base::PathExists(file));
+    EXPECT_EQ(
+        future.Get()->status,
+        blink::mojom::FileSystemAccessStatus::kNoModificationAllowedError);
+    EXPECT_TRUE(base::PathExists(file));
   }
 }
 
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl.h b/content/browser/file_system_access/file_system_access_file_writer_impl.h
index 2417dfd..d125f2e 100644
--- a/content/browser/file_system_access/file_system_access_file_writer_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_writer_impl.h
@@ -114,7 +114,7 @@
   // most filesystems, this move operation is atomic.
   storage::FileSystemURL swap_url_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // Shared write lock on the file. It is released on destruction.
+  // Exclusive write lock on the file. It is released on destruction.
   scoped_refptr<FileSystemAccessWriteLockManager::WriteLock> lock_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc b/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
index 20e006a9..4906b702d 100644
--- a/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
@@ -27,6 +27,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/shell/browser/shell.h"
+#include "media/base/media_switches.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 #include "media/base/video_util.h"
@@ -80,7 +81,10 @@
     : public ContentCaptureDeviceBrowserTestBase,
       public FrameTestUtil {
  public:
-  WebContentsVideoCaptureDeviceBrowserTest() = default;
+  WebContentsVideoCaptureDeviceBrowserTest() {
+    // TODO(https://crbug.com/1324757): tests should work with HiDPI enabled.
+    scoped_feature_list_.InitAndDisableFeature(media::kWebContentsCaptureHiDpi);
+  }
 
   WebContentsVideoCaptureDeviceBrowserTest(
       const WebContentsVideoCaptureDeviceBrowserTest&) = delete;
@@ -281,6 +285,9 @@
   }
 
   void WaitForFirstFrame() final { WaitForFrameWithColor(SK_ColorBLACK); }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests that the device refuses to start if the WebContents target was
diff --git a/content/browser/resources/aggregation_service/BUILD.gn b/content/browser/resources/aggregation_service/BUILD.gn
index 99fe826..00d2107d 100644
--- a/content/browser/resources/aggregation_service/BUILD.gn
+++ b/content/browser/resources/aggregation_service/BUILD.gn
@@ -13,7 +13,7 @@
 # Copy (via creating sym links) all the other files into the same folder for
 # ts_library.
 copy("copy_files") {
-  deps = [ "//content/browser/aggregation_service:mojo_bindings_webui_js" ]
+  deps = [ "//content/browser/aggregation_service:mojo_bindings_js__generator" ]
   sources = [
     "$root_gen_dir/mojom-webui/content/browser/aggregation_service/aggregation_service_internals.mojom-webui.js",
     "aggregation_service_internals.ts",
diff --git a/content/browser/resources/attribution_reporting/BUILD.gn b/content/browser/resources/attribution_reporting/BUILD.gn
index 069107c..61040daf 100644
--- a/content/browser/resources/attribution_reporting/BUILD.gn
+++ b/content/browser/resources/attribution_reporting/BUILD.gn
@@ -14,9 +14,9 @@
 # ts_library.
 copy("copy_files") {
   deps = [
-    "//components/attribution_reporting:mojom_webui_js",
-    "//content/browser/attribution_reporting:internals_mojo_bindings_webui_js",
-    "//content/browser/attribution_reporting:mojo_bindings_webui_js",
+    "//components/attribution_reporting:mojom_js__generator",
+    "//content/browser/attribution_reporting:internals_mojo_bindings_js__generator",
+    "//content/browser/attribution_reporting:mojo_bindings_js__generator",
   ]
   sources = [
     "$root_gen_dir/mojom-webui/components/attribution_reporting/source_registration_error.mojom-webui.js",
diff --git a/content/browser/resources/indexed_db/BUILD.gn b/content/browser/resources/indexed_db/BUILD.gn
index bbe4102..62b37125 100644
--- a/content/browser/resources/indexed_db/BUILD.gn
+++ b/content/browser/resources/indexed_db/BUILD.gn
@@ -38,12 +38,12 @@
   ]
 
   mojo_files_deps = [
-    "//$buckets_folder:buckets_webui_js",
-    "//$indexeddb_bucket_folder:mojom_bucket_webui_js",
-    "//$indexeddb_internal_folder:mojo_bindings_webui_js",
-    "//$quota_folder:quota_webui_js",
-    "//$schemeful_site_folder:mojom_schemeful_site_webui_js",
-    "//$storage_key_folder:storage_key_webui_js",
+    "//$buckets_folder:buckets_js__generator",
+    "//$indexeddb_bucket_folder:mojom_bucket_js__generator",
+    "//$indexeddb_internal_folder:mojo_bindings_js__generator",
+    "//$quota_folder:quota_js__generator",
+    "//$schemeful_site_folder:mojom_schemeful_site_js__generator",
+    "//$storage_key_folder:storage_key_js__generator",
   ]
 
   grit_output_dir = "$root_gen_dir/content"
diff --git a/content/browser/resources/process/BUILD.gn b/content/browser/resources/process/BUILD.gn
index 3a3d47c..f8180fc 100644
--- a/content/browser/resources/process/BUILD.gn
+++ b/content/browser/resources/process/BUILD.gn
@@ -7,7 +7,7 @@
 # Copy (via creating sym links) all the other files into the same folder for
 # ts_library.
 copy("copy_files") {
-  deps = [ "//content/browser/process_internals:mojo_bindings_webui_js" ]
+  deps = [ "//content/browser/process_internals:mojo_bindings_js__generator" ]
   sources = [
     "$root_gen_dir/mojom-webui/content/browser/process_internals/process_internals.mojom-webui.js",
     "process_internals.ts",
diff --git a/content/browser/resources/quota/BUILD.gn b/content/browser/resources/quota/BUILD.gn
index fe04cca..42dfb4c3 100644
--- a/content/browser/resources/quota/BUILD.gn
+++ b/content/browser/resources/quota/BUILD.gn
@@ -17,7 +17,7 @@
     "quota_internals_browser_proxy.ts",
   ]
 
-  mojo_files_deps = [ "//storage/browser/quota:mojo_bindings_webui_js" ]
+  mojo_files_deps = [ "//storage/browser/quota:mojo_bindings_js__generator" ]
   mojo_files = [ "$root_gen_dir/mojom-webui/storage/browser/quota/quota_internals.mojom-webui.js" ]
 
   ts_deps = [
diff --git a/content/browser/sandbox_parameters_mac.mm b/content/browser/sandbox_parameters_mac.mm
index 714c9f3..b14014e 100644
--- a/content/browser/sandbox_parameters_mac.mm
+++ b/content/browser/sandbox_parameters_mac.mm
@@ -19,6 +19,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/system/sys_info.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
@@ -236,7 +237,9 @@
                    << static_cast<int>(sandbox_type);
       break;
     // Setup parameters for sandbox types handled by embedders below.
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     case sandbox::mojom::Sandbox::kScreenAI:
+#endif
     case sandbox::mojom::Sandbox::kSpeechRecognition:
       SetupCommonSandboxParameters(client);
       CHECK(GetContentClient()->browser()->SetupEmbedderSandboxParameters(
diff --git a/content/browser/tracing/background_tracing_active_scenario.cc b/content/browser/tracing/background_tracing_active_scenario.cc
index 94a35e2..66d65dc 100644
--- a/content/browser/tracing/background_tracing_active_scenario.cc
+++ b/content/browser/tracing/background_tracing_active_scenario.cc
@@ -248,7 +248,7 @@
     // we abort tracing here.
     DCHECK_NE(config_->tracing_mode(), BackgroundTracingConfigImpl::SYSTEM);
     base::trace_event::TraceLog::GetInstance()->SetDisabled(
-        base::trace_event::TraceLog::GetInstance()->enabled_modes());
+        base::trace_event::TraceLog::RECORDING_MODE);
   }
 
   if (scenario_state_ == State::kAborted) {
diff --git a/content/browser/utility_sandbox_delegate_win.cc b/content/browser/utility_sandbox_delegate_win.cc
index fed4afb7..7ec09dd 100644
--- a/content/browser/utility_sandbox_delegate_win.cc
+++ b/content/browser/utility_sandbox_delegate_win.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 "base/files/file_path.h"
 #include "content/browser/utility_sandbox_delegate.h"
 
 #include "base/check.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/strings/utf_string_conversions.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "components/services/screen_ai/public/cpp/utilities.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_client.h"
@@ -209,6 +210,7 @@
   return true;
 }
 
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 bool ScreenAIPreSpawnTarget(sandbox::TargetConfig* config,
                             sandbox::mojom::Sandbox sandbox_type) {
   DCHECK(!config->IsConfigured());
@@ -236,6 +238,7 @@
                            library_binary_path.value().c_str());
   return result == sandbox::SBOX_ALL_OK;
 }
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
 }  // namespace
 
@@ -309,10 +312,12 @@
         return false;
     }
 
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     if (sandbox_type_ == sandbox::mojom::Sandbox::kScreenAI) {
       if (!ScreenAIPreSpawnTarget(config, sandbox_type_))
         return false;
     }
+#endif
 
     if (sandbox_type_ == sandbox::mojom::Sandbox::kSpeechRecognition) {
       auto result = config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 9dc02705..91b876e 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -642,6 +642,7 @@
     "junit/src/org/chromium/content/browser/selection/SmartSelectionEventProcessorTest.java",
     "junit/src/org/chromium/content/browser/sms/SmsProviderGmsTest.java",
     "junit/src/org/chromium/content/browser/webcontents/WebContentsImplTest.java",
+    "junit/src/org/chromium/content/browser/webcontents/WebContentsObserverProxyTest.java",
     "junit/src/org/chromium/content_public/browser/MessagePayloadTest.java",
   ]
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
index 3b374b4..c6c019d 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
@@ -19,6 +19,8 @@
 import org.chromium.ui.mojom.VirtualKeyboardMode;
 import org.chromium.url.GURL;
 
+import java.util.Iterator;
+
 /**
  * Serves as a compound observer proxy for dispatching WebContentsObserver callbacks,
  * avoiding redundant JNI-related work when there are multiple Java-based observers.
@@ -27,7 +29,6 @@
 class WebContentsObserverProxy extends WebContentsObserver {
     private long mNativeWebContentsObserverProxy;
     private final ObserverList<WebContentsObserver> mObservers;
-    private final RewindableIterator<WebContentsObserver> mObserversIterator;
     private int mObserverCallsCurrentlyHandling;
 
     /**
@@ -42,7 +43,6 @@
         mNativeWebContentsObserverProxy =
                 WebContentsObserverProxyJni.get().init(WebContentsObserverProxy.this, webContents);
         mObservers = new ObserverList<WebContentsObserver>();
-        mObserversIterator = mObservers.rewindableIterator();
         mObserverCallsCurrentlyHandling = 0;
     }
 
@@ -90,8 +90,9 @@
     @Override
     public void renderFrameCreated(GlobalRenderFrameHostId id) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().renderFrameCreated(id);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().renderFrameCreated(id);
         }
         finishObserverCall();
     }
@@ -104,8 +105,9 @@
     @Override
     public void renderFrameDeleted(GlobalRenderFrameHostId id) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().renderFrameDeleted(id);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().renderFrameDeleted(id);
         }
         finishObserverCall();
     }
@@ -116,8 +118,9 @@
         // Don't call handleObserverCall() and finishObserverCall() to explicitly allow a
         // WebContents to be destroyed while handling an this observer call. See
         // https://chromium-review.googlesource.com/c/chromium/src/+/2343269 for details
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().renderProcessGone();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().renderProcessGone();
         }
     }
 
@@ -125,8 +128,9 @@
     @CalledByNative
     public void didStartNavigationInPrimaryMainFrame(NavigationHandle navigation) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didStartNavigationInPrimaryMainFrame(navigation);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didStartNavigationInPrimaryMainFrame(navigation);
         }
         finishObserverCall();
     }
@@ -135,8 +139,9 @@
     @CalledByNative
     public void didStartNavigationNoop(NavigationHandle navigation) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didStartNavigationNoop(navigation);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didStartNavigationNoop(navigation);
         }
         finishObserverCall();
     }
@@ -145,8 +150,9 @@
     @CalledByNative
     public void didRedirectNavigation(NavigationHandle navigation) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didRedirectNavigation(navigation);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didRedirectNavigation(navigation);
         }
         finishObserverCall();
     }
@@ -155,8 +161,9 @@
     @CalledByNative
     public void didFinishNavigationInPrimaryMainFrame(NavigationHandle navigation) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFinishNavigationInPrimaryMainFrame(navigation);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFinishNavigationInPrimaryMainFrame(navigation);
         }
         finishObserverCall();
     }
@@ -165,8 +172,9 @@
     @CalledByNative
     public void didFinishNavigationNoop(NavigationHandle navigation) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFinishNavigationNoop(navigation);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFinishNavigationNoop(navigation);
         }
         finishObserverCall();
     }
@@ -175,8 +183,9 @@
     @CalledByNative
     public void didStartLoading(GURL url) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didStartLoading(url);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didStartLoading(url);
         }
         finishObserverCall();
     }
@@ -185,8 +194,9 @@
     @CalledByNative
     public void didStopLoading(GURL url, boolean isKnownValid) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didStopLoading(url, isKnownValid);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didStopLoading(url, isKnownValid);
         }
         finishObserverCall();
     }
@@ -195,8 +205,9 @@
     @CalledByNative
     public void loadProgressChanged(float progress) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().loadProgressChanged(progress);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().loadProgressChanged(progress);
         }
         finishObserverCall();
     }
@@ -205,8 +216,9 @@
     @CalledByNative
     public void didChangeVisibleSecurityState() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didChangeVisibleSecurityState();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didChangeVisibleSecurityState();
         }
         finishObserverCall();
     }
@@ -216,8 +228,9 @@
     public void didFailLoad(boolean isInPrimaryMainFrame, int errorCode, GURL failingUrl,
             @LifecycleState int frameLifecycleState) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFailLoad(
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFailLoad(
                     isInPrimaryMainFrame, errorCode, failingUrl, frameLifecycleState);
         }
         finishObserverCall();
@@ -227,8 +240,9 @@
     @CalledByNative
     public void didFirstVisuallyNonEmptyPaint() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFirstVisuallyNonEmptyPaint();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFirstVisuallyNonEmptyPaint();
         }
         finishObserverCall();
     }
@@ -237,8 +251,9 @@
     @CalledByNative
     public void wasShown() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().wasShown();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().wasShown();
         }
         finishObserverCall();
     }
@@ -247,8 +262,9 @@
     @CalledByNative
     public void wasHidden() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().wasHidden();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().wasHidden();
         }
         finishObserverCall();
     }
@@ -257,8 +273,9 @@
     @CalledByNative
     public void titleWasSet(String title) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().titleWasSet(title);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().titleWasSet(title);
         }
         finishObserverCall();
     }
@@ -267,8 +284,9 @@
     @CalledByNative
     public void primaryMainDocumentElementAvailable() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().primaryMainDocumentElementAvailable();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().primaryMainDocumentElementAvailable();
         }
         finishObserverCall();
     }
@@ -284,8 +302,9 @@
     public void didFinishLoadInPrimaryMainFrame(GlobalRenderFrameHostId rfhId, GURL url,
             boolean isKnownValid, @LifecycleState int rfhLifecycleState) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFinishLoadInPrimaryMainFrame(
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFinishLoadInPrimaryMainFrame(
                     rfhId, url, isKnownValid, rfhLifecycleState);
         }
         finishObserverCall();
@@ -303,8 +322,9 @@
     public void didFinishLoadNoop(GlobalRenderFrameHostId rfhId, GURL url, boolean isKnownValid,
             boolean isInPrimaryMainFrame, @LifecycleState int rfhLifecycleState) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFinishLoadNoop(
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didFinishLoadNoop(
                     rfhId, url, isKnownValid, isInPrimaryMainFrame, rfhLifecycleState);
         }
         finishObserverCall();
@@ -321,8 +341,9 @@
     public void documentLoadedInPrimaryMainFrame(
             GlobalRenderFrameHostId rfhId, @LifecycleState int rfhLifecycleState) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().documentLoadedInPrimaryMainFrame(rfhId, rfhLifecycleState);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().documentLoadedInPrimaryMainFrame(rfhId, rfhLifecycleState);
         }
         finishObserverCall();
     }
@@ -338,8 +359,9 @@
     public void documentLoadedInFrameNoop(GlobalRenderFrameHostId rfhId,
             boolean isInPrimaryMainFrame, @LifecycleState int rfhLifecycleState) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().documentLoadedInFrameNoop(
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().documentLoadedInFrameNoop(
                     rfhId, isInPrimaryMainFrame, rfhLifecycleState);
         }
         finishObserverCall();
@@ -349,8 +371,9 @@
     @CalledByNative
     public void navigationEntryCommitted(LoadCommittedDetails details) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().navigationEntryCommitted(details);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().navigationEntryCommitted(details);
         }
         finishObserverCall();
     }
@@ -359,8 +382,9 @@
     @CalledByNative
     public void navigationEntriesDeleted() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().navigationEntriesDeleted();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().navigationEntriesDeleted();
         }
         finishObserverCall();
     }
@@ -369,8 +393,9 @@
     @CalledByNative
     public void navigationEntriesChanged() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().navigationEntriesChanged();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().navigationEntriesChanged();
         }
         finishObserverCall();
     }
@@ -379,8 +404,9 @@
     @CalledByNative
     public void frameReceivedUserActivation() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().frameReceivedUserActivation();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().frameReceivedUserActivation();
         }
         finishObserverCall();
     }
@@ -389,8 +415,9 @@
     @CalledByNative
     public void didChangeThemeColor() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didChangeThemeColor();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didChangeThemeColor();
         }
         finishObserverCall();
     }
@@ -399,8 +426,9 @@
     @CalledByNative
     public void mediaStartedPlaying() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().mediaStartedPlaying();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().mediaStartedPlaying();
         }
         finishObserverCall();
     }
@@ -409,8 +437,9 @@
     @CalledByNative
     public void mediaStoppedPlaying() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().mediaStoppedPlaying();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().mediaStoppedPlaying();
         }
         finishObserverCall();
     }
@@ -419,8 +448,9 @@
     @CalledByNative
     public void hasEffectivelyFullscreenVideoChange(boolean isFullscreen) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().hasEffectivelyFullscreenVideoChange(isFullscreen);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().hasEffectivelyFullscreenVideoChange(isFullscreen);
         }
         finishObserverCall();
     }
@@ -429,8 +459,9 @@
     @CalledByNative
     public void didToggleFullscreenModeForTab(boolean enteredFullscreen, boolean willCauseResize) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didToggleFullscreenModeForTab(
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().didToggleFullscreenModeForTab(
                     enteredFullscreen, willCauseResize);
         }
         finishObserverCall();
@@ -440,8 +471,9 @@
     @CalledByNative
     public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().viewportFitChanged(value);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().viewportFitChanged(value);
         }
         finishObserverCall();
     }
@@ -450,8 +482,9 @@
     @CalledByNative
     public void virtualKeyboardModeChanged(@VirtualKeyboardMode.EnumType int mode) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().virtualKeyboardModeChanged(mode);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().virtualKeyboardModeChanged(mode);
         }
         finishObserverCall();
     }
@@ -460,8 +493,9 @@
     @CalledByNative
     public void onWebContentsFocused() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().onWebContentsFocused();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().onWebContentsFocused();
         }
         finishObserverCall();
     }
@@ -470,8 +504,9 @@
     @CalledByNative
     public void onWebContentsLostFocus() {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().onWebContentsLostFocus();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().onWebContentsLostFocus();
         }
         finishObserverCall();
     }
@@ -479,8 +514,9 @@
     @Override
     public void onTopLevelNativeWindowChanged(WindowAndroid windowAndroid) {
         handleObserverCall();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().onTopLevelNativeWindowChanged(windowAndroid);
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().onTopLevelNativeWindowChanged(windowAndroid);
         }
         finishObserverCall();
     }
@@ -492,15 +528,16 @@
         // Java-based WebContents) are quite different, so we explicitly avoid
         // calling it here.
         ThreadUtils.assertOnUiThread();
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().destroy();
+        RewindableIterator<WebContentsObserver> observersIterator = mObservers.rewindableIterator();
+        for (; observersIterator.hasNext();) {
+            observersIterator.next().destroy();
         }
         // All observer destroy() implementations should result in their removal
         // from the proxy.
         String remainingObservers = "These observers were not removed: ";
         if (!mObservers.isEmpty()) {
-            for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-                remainingObservers += mObserversIterator.next().getClass().getName() + " ";
+            for (observersIterator.rewind(); observersIterator.hasNext();) {
+                remainingObservers += observersIterator.next().getClass().getName() + " ";
             }
         }
         assert mObservers.isEmpty() : remainingObservers;
diff --git a/content/public/android/junit/src/org/chromium/content/browser/webcontents/WebContentsObserverProxyTest.java b/content/public/android/junit/src/org/chromium/content/browser/webcontents/WebContentsObserverProxyTest.java
new file mode 100644
index 0000000..623ae67
--- /dev/null
+++ b/content/public/android/junit/src/org/chromium/content/browser/webcontents/WebContentsObserverProxyTest.java
@@ -0,0 +1,73 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser.webcontents;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.content_public.browser.WebContentsObserver;
+
+/**
+ * Unit tests for {@link WebContentsObserverProxy}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class WebContentsObserverProxyTest {
+    @Mock
+    private WebContentsObserver mWebContentsObserver;
+    @Mock
+    private WebContentsObserver mWebContentsObserver2;
+    @Mock
+    private WebContentsObserverProxy.Natives mWebContentsObserverProxyJni;
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+
+    private WebContentsImpl mWebContentsImpl;
+    private final long mNativeWebContentsAndroid = 1;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mJniMocker.mock(WebContentsObserverProxyJni.TEST_HOOKS, mWebContentsObserverProxyJni);
+        when(mWebContentsObserverProxyJni.init(any(), any())).thenReturn(1L);
+    }
+
+    @Test
+    public void testChainingEvents() {
+        InOrder inOrder = Mockito.inOrder(mWebContentsObserver, mWebContentsObserver2);
+        final WebContentsObserverProxy proxy = new WebContentsObserverProxy(null);
+        proxy.addObserver(mWebContentsObserver);
+        proxy.addObserver(mWebContentsObserver2);
+        Mockito.doAnswer(new Answer<Void>() {
+                   @Override
+                   public Void answer(InvocationOnMock invocation) {
+                       proxy.navigationEntriesChanged();
+                       return null;
+                   }
+               })
+                .when(mWebContentsObserver)
+                .navigationEntriesDeleted();
+        proxy.navigationEntriesDeleted();
+        inOrder.verify(mWebContentsObserver).navigationEntriesDeleted();
+        inOrder.verify(mWebContentsObserver).navigationEntriesChanged();
+        inOrder.verify(mWebContentsObserver2).navigationEntriesChanged();
+        inOrder.verify(mWebContentsObserver2).navigationEntriesDeleted();
+        inOrder.verifyNoMoreInteractions();
+    }
+}
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.cc b/content/renderer/media/renderer_webaudiodevice_impl.cc
index 10aa412..fca5b131 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl.cc
@@ -97,6 +97,11 @@
       .output_params();
 }
 
+scoped_refptr<media::AudioRendererSink> GetNullAudioSink(
+    const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+  return base::MakeRefCounted<media::NullAudioSink>(task_runner);
+}
+
 }  // namespace
 
 std::unique_ptr<RendererWebAudioDeviceImpl> RendererWebAudioDeviceImpl::Create(
@@ -109,7 +114,8 @@
   return std::unique_ptr<RendererWebAudioDeviceImpl>(
       new RendererWebAudioDeviceImpl(
           sink_descriptor, layout, number_of_output_channels, latency_hint,
-          callback, session_id, base::BindOnce(&GetOutputDeviceParameters)));
+          callback, session_id, base::BindOnce(&GetOutputDeviceParameters),
+          base::BindRepeating(&GetNullAudioSink)));
 }
 
 RendererWebAudioDeviceImpl::RendererWebAudioDeviceImpl(
@@ -119,12 +125,14 @@
     const blink::WebAudioLatencyHint& latency_hint,
     WebAudioDevice::RenderCallback* callback,
     const base::UnguessableToken& session_id,
-    OutputDeviceParamsCallback device_params_cb)
+    OutputDeviceParamsCallback device_params_cb,
+    CreateSilentSinkCallback create_silent_sink_cb)
     : sink_descriptor_(sink_descriptor),
       latency_hint_(latency_hint),
       client_callback_(callback),
       session_id_(session_id),
-      frame_token_(sink_descriptor.Token()) {
+      frame_token_(sink_descriptor.Token()),
+      create_silent_sink_cb_(std::move(create_silent_sink_cb)) {
   DCHECK(client_callback_);
   SendLogMessage(base::StringPrintf("%s", __func__));
 
@@ -201,8 +209,7 @@
       sink_->Initialize(sink_params_, silent_sink_suspender_.get());
       break;
     case blink::WebAudioSinkDescriptor::kSilent:
-      sink_ =
-          base::MakeRefCounted<media::NullAudioSink>(GetSilentSinkTaskRunner());
+      sink_ = create_silent_sink_cb_.Run(GetSilentSinkTaskRunner());
       sink_->Initialize(sink_params_, this);
       break;
   }
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.h b/content/renderer/media/renderer_webaudiodevice_impl.h
index e567a61..62fc7d37 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.h
+++ b/content/renderer/media/renderer_webaudiodevice_impl.h
@@ -85,6 +85,10 @@
       const base::UnguessableToken& session_id,
       const std::string& device_id)>;
 
+  using CreateSilentSinkCallback =
+      base::RepeatingCallback<scoped_refptr<media::AudioRendererSink>(
+          const scoped_refptr<base::SequencedTaskRunner>& task_runner)>;
+
   RendererWebAudioDeviceImpl(
       const blink::WebAudioSinkDescriptor& sink_descriptor,
       media::ChannelLayout layout,
@@ -92,7 +96,8 @@
       const blink::WebAudioLatencyHint& latency_hint,
       blink::WebAudioDevice::RenderCallback* callback,
       const base::UnguessableToken& session_id,
-      OutputDeviceParamsCallback device_params_cb);
+      OutputDeviceParamsCallback device_params_cb,
+      CreateSilentSinkCallback create_silent_sink_cb);
 
  private:
   scoped_refptr<base::SingleThreadTaskRunner> GetSilentSinkTaskRunner();
@@ -135,6 +140,8 @@
   // Used to trigger one single textlog indicating that rendering started as
   // intended. Set to true once in the first call to the Render callback.
   bool is_rendering_ = false;
+
+  CreateSilentSinkCallback create_silent_sink_cb_;
 };
 
 }  // namespace content
diff --git a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
index 90b7e7df..9d707633 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
@@ -20,12 +20,40 @@
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/web/modules/media/audio/audio_device_factory.h"
 
-using testing::_;
+using ::testing::_;
+using ::testing::InSequence;
 
 namespace content {
 
 namespace {
 
+class MockAudioRendererSink : public media::AudioRendererSink {
+ public:
+  explicit MockAudioRendererSink() = default;
+  void Initialize(const media::AudioParameters& params,
+                  RenderCallback* callback) override {
+    callback_ = callback;
+  }
+  MOCK_METHOD(void, Start, (), (override));
+  MOCK_METHOD(void, Stop, (), (override));
+  MOCK_METHOD(void, Pause, (), (override));
+  MOCK_METHOD(void, Play, (), (override));
+  MOCK_METHOD(void, Flush, (), (override));
+  MOCK_METHOD(bool, SetVolume, (double volume), (override));
+  MOCK_METHOD(media::OutputDeviceInfo, GetOutputDeviceInfo, (), (override));
+  MOCK_METHOD(void,
+              GetOutputDeviceInfoAsync,
+              (OutputDeviceInfoCB info_cb),
+              (override));
+  MOCK_METHOD(bool, IsOptimizedForHardwareParameters, (), (override));
+  MOCK_METHOD(bool, CurrentThreadIsRenderingThread, (), (override));
+
+  media::AudioRendererSink::RenderCallback* callback_ = nullptr;
+
+ private:
+  ~MockAudioRendererSink() override = default;
+};
+
 constexpr int kHardwareSampleRate = 44100;
 constexpr int kHardwareBufferSize = 128;
 const blink::LocalFrameToken kFrameToken;
@@ -47,7 +75,8 @@
       int number_of_output_channels,
       const blink::WebAudioLatencyHint& latency_hint,
       blink::WebAudioDevice::RenderCallback* callback,
-      const base::UnguessableToken& session_id)
+      const base::UnguessableToken& session_id,
+      CreateSilentSinkCallback silent_sink_callback)
       : RendererWebAudioDeviceImpl(
             sink_descriptor,
             layout,
@@ -55,7 +84,8 @@
             latency_hint,
             callback,
             session_id,
-            base::BindOnce(&MockGetOutputDeviceParameters)) {}
+            base::BindOnce(&MockGetOutputDeviceParameters),
+            std::move(silent_sink_callback)) {}
 };
 
 }  // namespace
@@ -65,23 +95,36 @@
       public blink::AudioDeviceFactory,
       public testing::Test {
  public:
-  void Render(const blink::WebVector<float*>& destination_data,
-              uint32_t number_of_frames,
-              double delay,
-              double delay_timestamp,
-              size_t prior_frames_skipped) override {
-    called_render_ = true;
-  }
+  MOCK_METHOD(void,
+              Render,
+              (const blink::WebVector<float*>& destination_data,
+               uint32_t number_of_frames,
+               double delay,
+               double delay_timestamp,
+               size_t prior_frames_skipped),
+              (override));
 
  protected:
-  RendererWebAudioDeviceImplTest() {}
+  RendererWebAudioDeviceImplTest() {
+    mock_audio_renderer_sink_ = base::MakeRefCounted<MockAudioRendererSink>();
+  }
+
+  scoped_refptr<media::AudioRendererSink> MockCreateSilentSink(
+      const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+    return mock_audio_renderer_sink_;
+  }
 
   void SetupDevice(blink::WebAudioLatencyHint latencyHint) {
     blink::WebAudioSinkDescriptor sink_descriptor(
         blink::WebString::FromUTF8(std::string()), kFrameToken);
     webaudio_device_ = std::make_unique<RendererWebAudioDeviceImplUnderTest>(
         sink_descriptor, media::CHANNEL_LAYOUT_MONO, 1, latencyHint, this,
-        base::UnguessableToken());
+        base::UnguessableToken(),
+        base::BindRepeating(
+            &RendererWebAudioDeviceImplTest::MockCreateSilentSink,
+            // Guaranteed to be valid because |this| owns |webaudio_device_| and
+            // so will outlive it.
+            base::Unretained(this)));
     webaudio_device_->SetSilentSinkTaskRunnerForTesting(
         blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   }
@@ -93,7 +136,12 @@
         sink_descriptor, layout, channels,
         blink::WebAudioLatencyHint(
             blink::WebAudioLatencyHint::kCategoryInteractive),
-        this, base::UnguessableToken());
+        this, base::UnguessableToken(),
+        base::BindRepeating(
+            &RendererWebAudioDeviceImplTest::MockCreateSilentSink,
+            // Guaranteed to be valid because |this| owns |webaudio_device_| and
+            // so will outlive it.
+            base::Unretained(this)));
     webaudio_device_->SetSilentSinkTaskRunnerForTesting(
         blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   }
@@ -103,7 +151,12 @@
         sink_descriptor, media::CHANNEL_LAYOUT_MONO, 1,
         blink::WebAudioLatencyHint(
             blink::WebAudioLatencyHint::kCategoryInteractive),
-        this, base::UnguessableToken());
+        this, base::UnguessableToken(),
+        base::BindRepeating(
+            &RendererWebAudioDeviceImplTest::MockCreateSilentSink,
+            // Guaranteed to be valid because |this| owns |webaudio_device_| and
+            // so will outlive it.
+            base::Unretained(this)));
     webaudio_device_->SetSilentSinkTaskRunnerForTesting(
         blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   }
@@ -129,7 +182,7 @@
 
   std::unique_ptr<RendererWebAudioDeviceImpl> webaudio_device_;
   base::test::SingleThreadTaskEnvironment task_environment_;
-  bool called_render_ = false;
+  scoped_refptr<MockAudioRendererSink> mock_audio_renderer_sink_;
 };
 
 TEST_F(RendererWebAudioDeviceImplTest, ChannelLayout) {
@@ -229,39 +282,148 @@
   EXPECT_GE(balancedBufferSize, interactiveBufferSize);
 }
 
-TEST_F(RendererWebAudioDeviceImplTest, TestSilent) {
-  std::unique_ptr<media::AudioBus> audio_bus =
-      media::AudioBus::Create(1, kHardwareBufferSize);
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_RenderWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*this, Render).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
 
   // The WebAudioSinkDescriptor constructor with frame token will construct a
   // silent sink.
-  blink::WebAudioSinkDescriptor sink_descriptor(kFrameToken);
-  SetupDevice(sink_descriptor);
-
-  // Test public interface.
-  EXPECT_EQ(called_render_, false);
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
   webaudio_device_->Start();
-  task_environment_.RunUntilIdle();
-  EXPECT_EQ(called_render_, true);
-  webaudio_device_->SampleRate();
-  webaudio_device_->FramesPerBuffer();
-  webaudio_device_->SetDetectSilence(true);
-  webaudio_device_->SetDetectSilence(false);
+  mock_audio_renderer_sink_->callback_->Render(
+      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      media::AudioBus::Create(1, kHardwareBufferSize).get());
+  webaudio_device_->Stop();
+}
+
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_PauseResumeWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
+  webaudio_device_->Start();
   webaudio_device_->Pause();
   webaudio_device_->Resume();
   webaudio_device_->Stop();
+}
 
-  // Test repeated calls.
+TEST_F(RendererWebAudioDeviceImplTest,
+       NullSink_StartRenderStopStartRenderStopWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*this, Render).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*this, Render).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
   webaudio_device_->Start();
+  mock_audio_renderer_sink_->callback_->Render(
+      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      media::AudioBus::Create(1, kHardwareBufferSize).get());
+  webaudio_device_->Stop();
+  webaudio_device_->Start();
+  mock_audio_renderer_sink_->callback_->Render(
+      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      media::AudioBus::Create(1, kHardwareBufferSize).get());
+  webaudio_device_->Stop();
+}
+
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedStartWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
+  webaudio_device_->Start();
+  webaudio_device_->Start();
+  webaudio_device_->Stop();
+}
+
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedPauseWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(2);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
   webaudio_device_->Start();
   webaudio_device_->Pause();
   webaudio_device_->Pause();
+  webaudio_device_->Stop();
+}
+
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedResumeWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Pause).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(2);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
+  webaudio_device_->Start();
+  webaudio_device_->Pause();
   webaudio_device_->Resume();
   webaudio_device_->Resume();
   webaudio_device_->Stop();
-  webaudio_device_->Stop();
+}
 
-  task_environment_.RunUntilIdle();
+TEST_F(RendererWebAudioDeviceImplTest, NullSink_RepeatedStopWorks) {
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mock_audio_renderer_sink_, Start).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Play).Times(1);
+    EXPECT_CALL(*mock_audio_renderer_sink_, Stop).Times(1);
+  }
+
+  // The WebAudioSinkDescriptor constructor with frame token will construct a
+  // silent sink.
+  SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
+  webaudio_device_->Start();
+  webaudio_device_->Stop();
+  webaudio_device_->Stop();
 }
 
 }  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 31d12b8..e117d16 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2034,7 +2034,7 @@
     "grit/web_ui_mojo_test_resources_map.h",
     "web_ui_mojo_test_resources.pak",
   ]
-  deps = [ ":web_ui_test_mojo_bindings_webui_js" ]
+  deps = [ ":web_ui_test_mojo_bindings_js__generator" ]
 }
 
 static_library("run_all_unittests") {
@@ -2134,6 +2134,7 @@
     "../browser/browsing_data/same_site_data_remover_impl_unittest.cc",
     "../browser/browsing_topics/browsing_topics_site_data_manager_impl_unittest.cc",
     "../browser/browsing_topics/browsing_topics_site_data_storage_unittest.cc",
+    "../browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc",
     "../browser/browsing_topics/header_util_unittest.cc",
     "../browser/buckets/bucket_manager_host_unittest.cc",
     "../browser/buckets/bucket_utils_unittest.cc",
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 d633872..da1a026 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -375,6 +375,7 @@
 crbug.com/1382332 [ chromeos ] Pixel_MediaRecorderFromVideoElementWithOoprCanvasDisabled [ Failure ]
 crbug.com/1382332 [ android ] Pixel_MediaRecorderFromVideoElement [ Failure ]
 crbug.com/1382332 [ android ] Pixel_MediaRecorderFromVideoElementWithOoprCanvasDisabled [ Failure ]
+crbug.com/1382332 [ fuchsia ] Pixel_MediaRecorderFromVideoElementWithOoprCanvasDisabled [ Failure ]
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/device/bluetooth/floss/exported_callback_manager.h b/device/bluetooth/floss/exported_callback_manager.h
index 410ad974..e49da3d 100644
--- a/device/bluetooth/floss/exported_callback_manager.h
+++ b/device/bluetooth/floss/exported_callback_manager.h
@@ -9,6 +9,8 @@
 #include <unordered_map>
 #include <utility>
 
+#include "base/bind.h"
+#include "base/callback.h"
 #include "base/containers/contains.h"
 #include "base/logging.h"
 #include "dbus/bus.h"
@@ -20,10 +22,9 @@
 
 template <typename T>
 using MethodDelegate =
-    std::function<void(dbus::MethodCall*,
-                       base::WeakPtr<T>,
-                       dbus::ExportedObject::ResponseSender)>;
-
+    base::RepeatingCallback<void(dbus::MethodCall*,
+                                 base::WeakPtr<T>,
+                                 dbus::ExportedObject::ResponseSender)>;
 }
 
 // Private class helper.
@@ -110,17 +111,20 @@
   }
 
  public:
-  // Returns a lambda with captured |func| that parses D-Bus parameters and
-  // forwards it to |func|.
+  // Returns a RepeatingCallback with captured |func| that parses D-Bus
+  // parameters and forwards it to |func|.
   //
-  // Being a lambda has the benefit that the lambda invoker does not need to
+  // Being a RepeatingCallback has the benefit that the invoker does not need to
   // know the signature of |func| at compile time.
   static MethodDelegate<T> CreateForwarder(void (T::*func)(Args...)) {
-    return [func](dbus::MethodCall* method_call, base::WeakPtr<T> callback,
-                  dbus::ExportedObject::ResponseSender response_sender) {
-      Forward(method_call, base::BindOnce(func, callback),
-              std::move(response_sender));
-    };
+    return base::BindRepeating(
+        [](void (T::*func)(Args...), dbus::MethodCall* method_call,
+           base::WeakPtr<T> target,
+           dbus::ExportedObject::ResponseSender response_sender) {
+          Forward(method_call, base::BindOnce(func, target),
+                  std::move(response_sender));
+        },
+        func);
   }
 };
 
@@ -242,7 +246,7 @@
     DCHECK(method_name == method_call->GetMember())
         << "Method name from D-Bus does not match with the registered name";
 
-    delegate(method_call, exported_callback, std::move(response_sender));
+    delegate.Run(method_call, exported_callback, std::move(response_sender));
   }
 
   scoped_refptr<dbus::Bus> bus_;
diff --git a/extensions/browser/api/feedback_private/feedback_private_api.cc b/extensions/browser/api/feedback_private/feedback_private_api.cc
index 6ef7bb8..3670353c 100644
--- a/extensions/browser/api/feedback_private/feedback_private_api.cc
+++ b/extensions/browser/api/feedback_private/feedback_private_api.cc
@@ -375,4 +375,15 @@
   }
 }
 
+ExtensionFunction::ResponseAction FeedbackPrivateOpenFeedbackFunction::Run() {
+  std::unique_ptr<feedback_private::OpenFeedback::Params> params(
+      feedback_private::OpenFeedback::Params::Create(args()));
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate()->OpenFeedback(
+      browser_context(), params->source);
+
+  return RespondNow(NoArguments());
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/api/feedback_private/feedback_private_api.h b/extensions/browser/api/feedback_private/feedback_private_api.h
index 67aff72e..0dcc665 100644
--- a/extensions/browser/api/feedback_private/feedback_private_api.h
+++ b/extensions/browser/api/feedback_private/feedback_private_api.h
@@ -130,6 +130,16 @@
   void OnCompleted(api::feedback_private::LandingPageType type, bool success);
 };
 
+class FeedbackPrivateOpenFeedbackFunction : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("feedbackPrivate.openFeedback",
+                             FEEDBACKPRIVATE_OPENFEEDBACK)
+
+ protected:
+  ~FeedbackPrivateOpenFeedbackFunction() override = default;
+  ResponseAction Run() override;
+};
+
 }  // namespace extensions
 
 #endif  // EXTENSIONS_BROWSER_API_FEEDBACK_PRIVATE_FEEDBACK_PRIVATE_API_H_
diff --git a/extensions/browser/api/feedback_private/feedback_private_delegate.h b/extensions/browser/api/feedback_private/feedback_private_delegate.h
index 7113c30ee..7fb309f 100644
--- a/extensions/browser/api/feedback_private/feedback_private_delegate.h
+++ b/extensions/browser/api/feedback_private/feedback_private_delegate.h
@@ -85,6 +85,11 @@
   // feedback reports to the feedback server.
   virtual feedback::FeedbackUploader* GetFeedbackUploaderForContext(
       content::BrowserContext* context) const = 0;
+
+  // Opens the feedback report window.
+  virtual void OpenFeedback(
+      content::BrowserContext* context,
+      api::feedback_private::FeedbackSource source) const = 0;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 5db97a5..6961d7d 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1797,6 +1797,7 @@
   OS_DIAGNOSTICS_RUNNVMESELFTESTROUTINE = 1734,
   AUTOTESTPRIVATE_STARTFRAMECOUNTING = 1735,
   AUTOTESTPRIVATE_STOPFRAMECOUNTING = 1736,
+  FEEDBACKPRIVATE_OPENFEEDBACK = 1737,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 9d47807..9567b15 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -202,6 +202,17 @@
       ]
     }
   ],
+  "feedbackPrivate.openFeedback": {
+    "dependencies": [],
+    "channel": "stable",
+    "contexts": ["blessed_extension"],
+    "extension_types": ["extension"],
+    "allowlist": [
+      "2FC374607C2DF285634B67C64A2E356C607091C3",  // http://crbug.com/1352358
+      "3727DD3E564B6055387425027AD74C58784ACC15",  // http://crbug.com/1352358
+      "12E618C3C6E97495AAECF2AC12DEB082353241C6"   // http://crbug.com/1352358
+    ]
+  },
   "feedbackPrivate.readLogSource": [
     {
       "platforms": ["chromeos"],
diff --git a/extensions/common/api/extension_action/action_info.cc b/extensions/common/api/extension_action/action_info.cc
index f112a79..254cfaa 100644
--- a/extensions/common/api/extension_action/action_info.cc
+++ b/extensions/common/api/extension_action/action_info.cc
@@ -58,10 +58,12 @@
 ActionInfo::~ActionInfo() {}
 
 // static
-std::unique_ptr<ActionInfo> ActionInfo::Load(const Extension* extension,
-                                             Type type,
-                                             const base::DictionaryValue* dict,
-                                             std::u16string* error) {
+std::unique_ptr<ActionInfo> ActionInfo::Load(
+    const Extension* extension,
+    Type type,
+    const base::DictionaryValue* dict,
+    std::vector<InstallWarning>* install_warnings,
+    std::u16string* error) {
   auto result = std::make_unique<ActionInfo>(type);
 
   // Read the page action |default_icon| (optional).
@@ -113,16 +115,25 @@
     }
 
     if (!url_str->empty()) {
-      // An empty string is treated as having no popup.
-      result->default_popup_url =
-          Extension::GetResourceURL(extension->url(), *url_str);
-      if (!result->default_popup_url.is_valid()) {
+      GURL popup_url = Extension::GetResourceURL(extension->url(), *url_str);
+
+      if (!popup_url.is_valid()) {
         *error = errors::kInvalidActionDefaultPopup;
         return nullptr;
       }
+
+      // Check popup is only for this extension.
+      if (extension->origin().IsSameOriginWith(popup_url)) {
+        result->default_popup_url = popup_url;
+      } else {
+        install_warnings->push_back(extensions::InstallWarning(
+            extensions::manifest_errors::kInvalidExtensionOriginPopup,
+            GetManifestKeyForActionType(type),
+            extensions::manifest_keys::kActionDefaultPopup));
+      }
     } else {
-      DCHECK(result->default_popup_url.is_empty())
-          << "Shouldn't be possible for the popup to be set.";
+      // An empty string is treated as having no popup.
+      DCHECK(result->default_popup_url.is_empty());
     }
   }
 
diff --git a/extensions/common/api/extension_action/action_info.h b/extensions/common/api/extension_action/action_info.h
index e4cdb58d..d3bec75 100644
--- a/extensions/common/api/extension_action/action_info.h
+++ b/extensions/common/api/extension_action/action_info.h
@@ -37,11 +37,14 @@
   ActionInfo(const ActionInfo& other);
   ~ActionInfo();
 
-  // Loads an ActionInfo from the given DictionaryValue.
-  static std::unique_ptr<ActionInfo> Load(const Extension* extension,
-                                          Type type,
-                                          const base::DictionaryValue* dict,
-                                          std::u16string* error);
+  // Loads an ActionInfo from the given DictionaryValue. Populating
+  // `install_warnings` if issues are encountered when parsing the manifest.
+  static std::unique_ptr<ActionInfo> Load(
+      const Extension* extension,
+      Type type,
+      const base::DictionaryValue* dict,
+      std::vector<InstallWarning>* install_warnings,
+      std::u16string* error);
 
   // TODO(jlulejian): Rather than continue to grow this list of static helper
   // methods, move them to a action_helper.h class similar to
diff --git a/extensions/common/api/feedback_private.idl b/extensions/common/api/feedback_private.idl
index 3ae66ff..8ca3d7db 100644
--- a/extensions/common/api/feedback_private.idl
+++ b/extensions/common/api/feedback_private.idl
@@ -160,6 +160,9 @@
     uptime
   };
 
+  // Source of the feedback.
+  enum FeedbackSource {quickoffice};
+
   // Input parameters for a readLogSource() call.
   dictionary ReadLogSourceParams {
     // The log source from which to read.
@@ -208,6 +211,9 @@
     // Returns the system information dictionary.
     static void getSystemInformation(GetSystemInformationCallback callback);
 
+    // Opens the feedback report window.
+    static void openFeedback(FeedbackSource source);
+
     // Sends a feedback report.
     // |loadSystemInfo|: Optional flag when present and is true, the backend
     // should load system information before sending the report. This is added
diff --git a/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc b/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc
index b6808ca..c35d559 100644
--- a/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc
+++ b/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc
@@ -40,11 +40,10 @@
   EXPECT_EQ(0u, permission->entries().size());
 }
 
-static std::unique_ptr<base::Value> ParsePermissionJSON(
-    const std::string& json) {
-  std::unique_ptr<base::Value> result(base::JSONReader::ReadDeprecated(json));
+static base::Value ParsePermissionJSON(const std::string& json) {
+  absl::optional<base::Value> result = base::JSONReader::Read(json);
   EXPECT_TRUE(result) << "Invalid JSON string: " << json;
-  return result;
+  return std::move(result.value());
 }
 
 static std::unique_ptr<SocketsManifestPermission> PermissionFromValue(
@@ -58,8 +57,8 @@
 
 static std::unique_ptr<SocketsManifestPermission> PermissionFromJSON(
     const std::string& json) {
-  std::unique_ptr<base::Value> value(ParsePermissionJSON(json));
-  return PermissionFromValue(*value);
+  base::Value value = ParsePermissionJSON(json);
+  return PermissionFromValue(value);
 }
 
 struct CheckFormatEntry {
@@ -270,34 +269,31 @@
 }
 
 TEST(SocketsManifestPermissionTest, FromToValue) {
-  std::unique_ptr<base::Value> udp_send(
-      ParsePermissionJSON(kUdpBindPermission));
-  std::unique_ptr<base::Value> udp_bind(
-      ParsePermissionJSON(kUdpSendPermission));
-  std::unique_ptr<base::Value> tcp_connect(
-      ParsePermissionJSON(kTcpConnectPermission));
-  std::unique_ptr<base::Value> tcp_server_listen(
-      ParsePermissionJSON(kTcpServerListenPermission));
+  base::Value udp_send = ParsePermissionJSON(kUdpBindPermission);
+  base::Value udp_bind = ParsePermissionJSON(kUdpSendPermission);
+  base::Value tcp_connect = ParsePermissionJSON(kTcpConnectPermission);
+  base::Value tcp_server_listen =
+      ParsePermissionJSON(kTcpServerListenPermission);
 
   // FromValue()
   std::unique_ptr<SocketsManifestPermission> permission1(
       new SocketsManifestPermission());
-  EXPECT_TRUE(permission1->FromValue(udp_send.get()));
+  EXPECT_TRUE(permission1->FromValue(&udp_send));
   EXPECT_EQ(2u, permission1->entries().size());
 
   std::unique_ptr<SocketsManifestPermission> permission2(
       new SocketsManifestPermission());
-  EXPECT_TRUE(permission2->FromValue(udp_bind.get()));
+  EXPECT_TRUE(permission2->FromValue(&udp_bind));
   EXPECT_EQ(2u, permission2->entries().size());
 
   std::unique_ptr<SocketsManifestPermission> permission3(
       new SocketsManifestPermission());
-  EXPECT_TRUE(permission3->FromValue(tcp_connect.get()));
+  EXPECT_TRUE(permission3->FromValue(&tcp_connect));
   EXPECT_EQ(2u, permission3->entries().size());
 
   std::unique_ptr<SocketsManifestPermission> permission4(
       new SocketsManifestPermission());
-  EXPECT_TRUE(permission4->FromValue(tcp_server_listen.get()));
+  EXPECT_TRUE(permission4->FromValue(&tcp_server_listen));
   EXPECT_EQ(2u, permission4->entries().size());
 
   // ToValue()
diff --git a/extensions/common/manifest_handlers/extension_action_handler.cc b/extensions/common/manifest_handlers/extension_action_handler.cc
index 59444530..e890beca 100644
--- a/extensions/common/manifest_handlers/extension_action_handler.cc
+++ b/extensions/common/manifest_handlers/extension_action_handler.cc
@@ -16,10 +16,9 @@
 #include "extensions/common/image_util.h"
 #include "extensions/common/manifest_constants.h"
 
-// Adds `extensions::InstallWarning`s to an `extensions::Extension` if the
-// `default_popup` value for the action is not same origin, or doesn't
-// exist in the filesystem.
-void SetWarningsForInvalidDefaultPopup(
+// Adds `extensions::InstallWarning`s to `warnings` if the`default_popup` value
+// for the action doesn't exist in the filesystem.
+void SetWarningsForNonExistentDefaultPopup(
     const extensions::ActionInfo* action,
     const char* manifest_key,
     const extensions::Extension* extension,
@@ -35,13 +34,7 @@
   base::FilePath resource_path =
       extension->GetResource(relative_path).GetFilePath();
 
-  // Check popup is only for this extension.
-  if (!extension->origin().IsSameOriginWith(default_popup_url)) {
-    warnings->push_back(extensions::InstallWarning(
-        extensions::manifest_errors::kInvalidExtensionOriginPopup, manifest_key,
-        extensions::manifest_keys::kActionDefaultPopup));
-    // Check that the popup file actually exists on filesystem.
-  } else if (resource_path.empty() || !base::PathExists(resource_path)) {
+  if (resource_path.empty() || !base::PathExists(resource_path)) {
     warnings->push_back(extensions::InstallWarning(
         extensions::manifest_errors::kNonexistentDefaultPopup, manifest_key,
         extensions::manifest_keys::kActionDefaultPopup));
@@ -94,8 +87,10 @@
       return false;
     }
 
+    std::vector<InstallWarning> install_warnings;
     std::unique_ptr<ActionInfo> action_info =
-        ActionInfo::Load(extension, type, dict, error);
+        ActionInfo::Load(extension, type, dict, &install_warnings, error);
+    extension->AddInstallWarnings(std::move(install_warnings));
     if (!action_info)
       return false;  // Failed to parse extension action definition.
 
@@ -137,7 +132,8 @@
       ActionInfo::GetManifestKeyForActionType(action->type);
   DCHECK(manifest_key);
 
-  SetWarningsForInvalidDefaultPopup(action, manifest_key, extension, warnings);
+  SetWarningsForNonExistentDefaultPopup(action, manifest_key, extension,
+                                        warnings);
 
   // Empty default icon is valid.
   if (action->default_icon.empty())
diff --git a/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.cc b/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.cc
index 78c9a396..3a652f6 100644
--- a/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.cc
+++ b/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.cc
@@ -77,4 +77,10 @@
   return feedback::FeedbackUploaderFactory::GetForBrowserContext(context);
 }
 
+void ShellFeedbackPrivateDelegate::OpenFeedback(
+    content::BrowserContext* context,
+    api::feedback_private::FeedbackSource source) const {
+  NOTIMPLEMENTED();
+}
+
 }  // namespace extensions
diff --git a/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.h b/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.h
index eb002a28..3135843 100644
--- a/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.h
+++ b/extensions/shell/browser/api/feedback_private/shell_feedback_private_delegate.h
@@ -42,6 +42,9 @@
   void NotifyFeedbackDelayed() const override;
   feedback::FeedbackUploader* GetFeedbackUploaderForContext(
       content::BrowserContext* context) const override;
+  void OpenFeedback(
+      content::BrowserContext* context,
+      api::feedback_private::FeedbackSource source) const override;
 };
 
 }  // namespace extensions
diff --git a/fuchsia_web/runners/cast/cast_component.cc b/fuchsia_web/runners/cast/cast_component.cc
index 927f1cd..c79a8aa 100644
--- a/fuchsia_web/runners/cast/cast_component.cc
+++ b/fuchsia_web/runners/cast/cast_component.cc
@@ -195,8 +195,13 @@
   fuchsia::web::ContentAreaSettings settings;
   // Disable scrollbars on all Cast applications.
   settings.set_hide_scrollbars(true);
-  // Get the theme from the system service.
-  settings.set_theme(fuchsia::settings::ThemeType::DEFAULT);
+
+  // Get the theme from `fuchsia.settings.Display`, except in headless mode
+  // where the service may not be available.
+  if (!is_headless_) {
+    settings.set_theme(fuchsia::settings::ThemeType::DEFAULT);
+  }
+
   frame()->SetContentAreaSettings(std::move(settings));
 }
 
diff --git a/gpu/command_buffer/service/skia_utils.cc b/gpu/command_buffer/service/skia_utils.cc
index 2b4f0c958..9920232a 100644
--- a/gpu/command_buffer/service/skia_utils.cc
+++ b/gpu/command_buffer/service/skia_utils.cc
@@ -107,12 +107,22 @@
     const gles2::FeatureInfo* feature_info,
     viz::ResourceFormat resource_format,
     sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe) {
+  GLenum gl_storage_format = viz::TextureStorageFormat(
+      resource_format,
+      feature_info->feature_flags().angle_rgbx_internal_format);
+  return GetGrGLBackendTextureFormat(feature_info, gl_storage_format,
+                                     gr_context_thread_safe);
+}
+
+GLuint GetGrGLBackendTextureFormat(
+    const gles2::FeatureInfo* feature_info,
+    GLenum gl_storage_format,
+    sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe) {
+  // TODO(hitawala): Internalize the skia version specifics to a
+  // SharedImageFormat util function after getting the TextureStorageFormat.
   const gl::GLVersionInfo* version_info = &feature_info->gl_version_info();
-  GLuint internal_format = gl::GetInternalFormat(
-      version_info,
-      viz::TextureStorageFormat(
-          resource_format,
-          feature_info->feature_flags().angle_rgbx_internal_format));
+  GLuint internal_format =
+      gl::GetInternalFormat(version_info, gl_storage_format);
 
   bool use_version_es2 = false;
 #if BUILDFLAG(IS_ANDROID)
@@ -135,7 +145,7 @@
   }
 
   // Map ETC1 to ETC2 type depending on conversion by skia
-  if (resource_format == viz::ResourceFormat::ETC1) {
+  if (gl_storage_format == GL_ETC1_RGB8_OES) {
     GrGLFormat gr_gl_format =
         gr_context_thread_safe
             ->compressedBackendFormat(SkImage::kETC1_CompressionType)
@@ -163,6 +173,21 @@
                          viz::ResourceFormat resource_format,
                          sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe,
                          GrBackendTexture* gr_texture) {
+  GLenum gl_storage_format = viz::TextureStorageFormat(
+      resource_format,
+      feature_info->feature_flags().angle_rgbx_internal_format);
+  return GetGrBackendTexture(feature_info, target, size, service_id,
+                             gl_storage_format, gr_context_thread_safe,
+                             gr_texture);
+}
+
+bool GetGrBackendTexture(const gles2::FeatureInfo* feature_info,
+                         GLenum target,
+                         const gfx::Size& size,
+                         GLuint service_id,
+                         GLenum gl_storage_format,
+                         sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe,
+                         GrBackendTexture* gr_texture) {
   if (target != GL_TEXTURE_2D && target != GL_TEXTURE_RECTANGLE_ARB &&
       target != GL_TEXTURE_EXTERNAL_OES) {
     LOG(ERROR) << "GetGrBackendTexture: invalid texture target.";
@@ -173,7 +198,7 @@
   texture_info.fID = service_id;
   texture_info.fTarget = target;
   texture_info.fFormat = GetGrGLBackendTextureFormat(
-      feature_info, resource_format, gr_context_thread_safe);
+      feature_info, gl_storage_format, gr_context_thread_safe);
   *gr_texture = GrBackendTexture(size.width(), size.height(), GrMipMapped::kNo,
                                  texture_info);
   return true;
diff --git a/gpu/command_buffer/service/skia_utils.h b/gpu/command_buffer/service/skia_utils.h
index 70631f53..8a580fad 100644
--- a/gpu/command_buffer/service/skia_utils.h
+++ b/gpu/command_buffer/service/skia_utils.h
@@ -51,12 +51,31 @@
 GPU_GLES2_EXPORT GrContextOptions
 GetDefaultGrContextOptions(GrContextType type);
 
-// Returns internal gl format of texture for Skia
+// Returns internal gl format of texture for Skia for given `resource_format`.
+// NOTE: This is being deprecated. Use below function with gl_storage_format.
 GPU_GLES2_EXPORT GLuint GetGrGLBackendTextureFormat(
     const gles2::FeatureInfo* feature_info,
     viz::ResourceFormat resource_format,
     sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe);
 
+// Returns internal gl format of texture for Skia for given `gl_storage_format`.
+GPU_GLES2_EXPORT GLuint GetGrGLBackendTextureFormat(
+    const gles2::FeatureInfo* feature_info,
+    GLenum gl_storage_format,
+    sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe);
+
+// Creates a GrBackendTexture from a service ID. Skia does not take ownership.
+// Returns true on success.
+// NOTE: This is being deprecated. Use below function with gl_storage_format.
+GPU_GLES2_EXPORT bool GetGrBackendTexture(
+    const gles2::FeatureInfo* feature_info,
+    GLenum target,
+    const gfx::Size& size,
+    GLuint service_id,
+    viz::ResourceFormat resource_format,
+    sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe,
+    GrBackendTexture* gr_texture);
+
 // Creates a GrBackendTexture from a service ID. Skia does not take ownership.
 // Returns true on success.
 GPU_GLES2_EXPORT bool GetGrBackendTexture(
@@ -64,7 +83,7 @@
     GLenum target,
     const gfx::Size& size,
     GLuint service_id,
-    viz::ResourceFormat resource_format,
+    GLenum gl_storage_format,
     sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe,
     GrBackendTexture* gr_texture);
 
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc
index e48031d1..23e4d5b 100644
--- a/headless/app/headless_shell.cc
+++ b/headless/app/headless_shell.cc
@@ -58,9 +58,15 @@
 
 namespace {
 
-// Default file name for screenshot. Can be overriden by "--screenshot" switch.
+#if BUILDFLAG(IS_WIN)
+const wchar_t kAboutBlank[] = L"about:blank";
+#else
+const char kAboutBlank[] = "about:blank";
+#endif
+
+// Default file name for screenshot. Can be overridden by "--screenshot" switch.
 const char kDefaultScreenshotFileName[] = "screenshot.png";
-// Default file name for pdf. Can be overriden by "--print-to-pdf" switch.
+// Default file name for pdf. Can be overridden by "--print-to-pdf" switch.
 const char kDefaultPDFFileName[] = "output.pdf";
 
 GURL ConvertArgumentToURL(const base::CommandLine::StringType& arg) {
@@ -136,15 +142,9 @@
       base::CommandLine::ForCurrentProcess()->GetArgs();
 
   // If no explicit URL is present, navigate to about:blank, unless we're being
-  // driven by debugger.
-  if (args.empty() && !base::CommandLine::ForCurrentProcess()->HasSwitch(
-                          switches::kRemoteDebuggingPipe)) {
-#if BUILDFLAG(IS_WIN)
-    args.push_back(L"about:blank");
-#else
-    args.push_back("about:blank");
-#endif
-  }
+  // driven by a debugger.
+  if (args.empty() && !IsRemoteDebuggingEnabled())
+    args.push_back(kAboutBlank);
 
   if (!args.empty()) {
     file_task_runner_->PostTaskAndReplyWithResult(
diff --git a/infra/config/generated/builders/ci/Linux MSan Focal/properties.json b/infra/config/generated/builders/ci/Linux MSan Focal/properties.json
deleted file mode 100644
index 5fc9bed..0000000
--- a/infra/config/generated/builders/ci/Linux MSan Focal/properties.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "Linux MSan Focal",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium_msan"
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "Linux MSan Focal",
-          "project": "chromium"
-        }
-      ],
-      "mirroring_builder_group_and_names": [
-        {
-          "builder": "linux_chromium_msan_focal",
-          "group": "tryserver.chromium.linux"
-        }
-      ]
-    }
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 500,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/linux-lacros-tester-rel-reviver/properties.json b/infra/config/generated/builders/ci/linux-lacros-tester-rel-reviver/properties.json
deleted file mode 100644
index feb8841..0000000
--- a/infra/config/generated/builders/ci/linux-lacros-tester-rel-reviver/properties.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "linux-lacros-tester-rel-reviver",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-chromiumos-archive",
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_arch": "intel",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "chromeos"
-                ],
-                "config": "chromium_no_telemetry_dependencies"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "linux-lacros-tester-rel-reviver",
-          "project": "chromium"
-        }
-      ],
-      "mirroring_builder_group_and_names": [
-        {
-          "builder": "linux-lacros-tester-rel-reviver",
-          "group": "tryserver.chromium.chromiumos"
-        }
-      ]
-    }
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 250,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/fuchsia-arm64-cast-receiver-rel/properties.json b/infra/config/generated/builders/try/fuchsia-arm64-cast-receiver-rel/properties.json
index fc64ef6..d8982c4 100644
--- a/infra/config/generated/builders/try/fuchsia-arm64-cast-receiver-rel/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-arm64-cast-receiver-rel/properties.json
@@ -43,14 +43,9 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-arm64-chrome-rel/properties.json b/infra/config/generated/builders/try/fuchsia-arm64-chrome-rel/properties.json
index f8dc5d5..d5dfc3bc 100644
--- a/infra/config/generated/builders/try/fuchsia-arm64-chrome-rel/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-arm64-chrome-rel/properties.json
@@ -51,7 +51,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-compile-x64-dbg/properties.json b/infra/config/generated/builders/try/fuchsia-compile-x64-dbg/properties.json
index 425f812..7d8cdca 100644
--- a/infra/config/generated/builders/try/fuchsia-compile-x64-dbg/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-compile-x64-dbg/properties.json
@@ -42,14 +42,9 @@
       "is_compile_only": true
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-fyi-arm64-dbg/properties.json b/infra/config/generated/builders/try/fuchsia-fyi-arm64-dbg/properties.json
index 86da2cf..fe36dcb 100644
--- a/infra/config/generated/builders/try/fuchsia-fyi-arm64-dbg/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-fyi-arm64-dbg/properties.json
@@ -44,14 +44,9 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-fyi-x64-dbg/properties.json b/infra/config/generated/builders/try/fuchsia-fyi-x64-dbg/properties.json
index 25138b23..edeadbf5 100644
--- a/infra/config/generated/builders/try/fuchsia-fyi-x64-dbg/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-fyi-x64-dbg/properties.json
@@ -41,14 +41,9 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-official/properties.json b/infra/config/generated/builders/try/fuchsia-official/properties.json
index ce10abd..e2c3ae4c 100644
--- a/infra/config/generated/builders/try/fuchsia-official/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-official/properties.json
@@ -39,10 +39,10 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "jobs": 300,
+    "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel-compilator/properties.json b/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel-compilator/properties.json
index 69e3416a..3c93621 100644
--- a/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel-compilator/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-x64-cast-receiver-rel-compilator/properties.json
@@ -41,12 +41,6 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "jobs": 150,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
     "jobs": 300,
diff --git a/infra/config/generated/builders/try/fuchsia-x64-chrome-rel/properties.json b/infra/config/generated/builders/try/fuchsia-x64-chrome-rel/properties.json
index 46d7d88f..b5ea470a 100644
--- a/infra/config/generated/builders/try/fuchsia-x64-chrome-rel/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-x64-chrome-rel/properties.json
@@ -49,7 +49,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-x64-rel/properties.json b/infra/config/generated/builders/try/fuchsia-x64-rel/properties.json
index 7ccfe95b..da8d0a11 100644
--- a/infra/config/generated/builders/try/fuchsia-x64-rel/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-x64-rel/properties.json
@@ -48,7 +48,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/fuchsia-x64-workstation/properties.json b/infra/config/generated/builders/try/fuchsia-x64-workstation/properties.json
index f334520..3ca2c42 100644
--- a/infra/config/generated/builders/try/fuchsia-x64-workstation/properties.json
+++ b/infra/config/generated/builders/try/fuchsia-x64-workstation/properties.json
@@ -49,7 +49,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
+    "jobs": 300,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/linux-dawn-rel/properties.json b/infra/config/generated/builders/try/linux-dawn-rel/properties.json
index 6efce04..a4b14b2 100644
--- a/infra/config/generated/builders/try/linux-dawn-rel/properties.json
+++ b/infra/config/generated/builders/try/linux-dawn-rel/properties.json
@@ -114,11 +114,6 @@
       ]
     }
   },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
     "jobs": 150,
diff --git a/infra/config/generated/builders/try/linux-lacros-tester-rel-reviver/properties.json b/infra/config/generated/builders/try/linux-lacros-tester-rel-reviver/properties.json
deleted file mode 100644
index 57e2e775..0000000
--- a/infra/config/generated/builders/try/linux-lacros-tester-rel-reviver/properties.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "linux-lacros-tester-rel-reviver",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-chromiumos-archive",
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_arch": "intel",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "chromeos"
-                ],
-                "config": "chromium_no_telemetry_dependencies"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "linux-lacros-tester-rel-reviver",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-untrusted",
-    "jobs": 150,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "tryserver.chromium.chromiumos",
-  "recipe": "chromium_trybot"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux_chromium_msan_focal/properties.json b/infra/config/generated/builders/try/linux_chromium_msan_focal/properties.json
deleted file mode 100644
index 79e84aff..0000000
--- a/infra/config/generated/builders/try/linux_chromium_msan_focal/properties.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "Linux MSan Focal",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium_msan"
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "Linux MSan Focal",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-untrusted",
-    "jobs": 300,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "tryserver.chromium.linux",
-  "recipe": "chromium_trybot"
-}
\ No newline at end of file
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 0721a769..e123a0a 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -2606,10 +2606,6 @@
         includable_only: true
       }
       builders {
-        name: "chromium/try/linux-lacros-tester-rel-reviver"
-        includable_only: true
-      }
-      builders {
         name: "chromium/try/linux-lacros-version-skew-fyi"
         includable_only: true
       }
@@ -2974,10 +2970,6 @@
         }
       }
       builders {
-        name: "chromium/try/linux_chromium_msan_focal"
-        includable_only: true
-      }
-      builders {
         name: "chromium/try/linux_chromium_msan_rel_ng"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 195594a..0281c56 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -13839,7 +13839,7 @@
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-20.04"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -13924,102 +13924,13 @@
       }
     }
     builders {
-      name: "Linux MSan Focal"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-20.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/Linux MSan Focal/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      priority: 35
-      execution_timeout_secs: 57600
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        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/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "Linux MSan Tests"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-20.04"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:0"
       exe {
@@ -26371,6 +26282,34 @@
       }
       properties:
         '{'
+        '  "$build/avd_packager": {'
+        '    "avd_configs": ['
+        '      "tools/android/avd/proto/creation/generic_android19.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android22.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android23.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android24.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android24.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android25.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android25.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android27.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android27.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android28.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android28.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android29.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android30.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android30.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android31.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android31.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android32_foldable.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb",'
+        '      "tools/android/avd/proto/creation/generic_android33.textpb",'
+        '      "tools/android/avd/proto/creation/generic_playstore_android33.textpb"'
+        '    ],'
+        '    "gclient_apply_config": ['
+        '      "android"'
+        '    ],'
+        '    "gclient_config": "chromium"'
+        '  },'
         '  "$recipe_engine/resultdb/test_presentation": {'
         '    "column_keys": [],'
         '    "grouping_keys": ['
@@ -39788,95 +39727,6 @@
       }
     }
     builders {
-      name: "linux-lacros-tester-rel-reviver"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      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/linux-lacros-tester-rel-reviver/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      priority: 35
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        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/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux-lacros-version-skew-fyi"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -54821,6 +54671,53 @@
       }
     }
     builders {
+      name: "lacros-coordinator"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/main"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "recipe": "chromium_polymorphic/launcher",'
+        '  "runner_builder": {'
+        '    "bucket": "reviver",'
+        '    "builder": "runner",'
+        '    "project": "chromium"'
+        '  },'
+        '  "target_builders": ['
+        '    {'
+        '      "builder_id": {'
+        '        "bucket": "ci",'
+        '        "builder": "linux-lacros-tester-rel",'
+        '        "project": "chromium"'
+        '      }'
+        '    }'
+        '  ]'
+        '}'
+      service_account: "reviver-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.buildbucket.omit_python2"
+        value: 0
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
       name: "runner"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -65385,15 +65282,9 @@
         '      "fuchsia_sizes"'
         '    ]'
         '  },'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "jobs": 150,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
-        '    "jobs": 150,'
+        '    "jobs": 300,'
         '    "metrics_project": "chromium-reclient-metrics"'
         '  },'
         '  "$recipe_engine/resultdb/test_presentation": {'
@@ -65493,12 +65384,6 @@
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "jobs": 150,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
         '    "jobs": 150,'
@@ -65711,14 +65596,9 @@
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
-        '    "jobs": 150,'
+        '    "jobs": 300,'
         '    "metrics_project": "chromium-reclient-metrics"'
         '  },'
         '  "$recipe_engine/resultdb/test_presentation": {'
@@ -78799,116 +78679,6 @@
       description_html: "This is the compilator half of an orchestrator + compilator pair of builders. The orchestrator is <a href=\"https://ci.chromium.org/p/chromium/builders/try/linux-lacros-rel\">linux-lacros-rel</a>."
     }
     builders {
-      name: "linux-lacros-tester-rel-reviver"
-      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/linux-lacros-tester-rel-reviver/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "tryserver.chromium.chromiumos",'
-        '  "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: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "enable_weetbix_queries"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.enable_weetbix_exonerations"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.retry_weak_exonerations"
-        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/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux-lacros-version-skew-fyi"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -83600,122 +83370,12 @@
       }
     }
     builders {
-      name: "linux_chromium_msan_focal"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-20.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/linux_chromium_msan_focal/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "tryserver.chromium.linux",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium_trybot"'
-        '}'
-      execution_timeout_secs: 57600
-      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: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "enable_weetbix_queries"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.enable_weetbix_exonerations"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.retry_weak_exonerations"
-        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/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux_chromium_msan_rel_ng"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-20.04"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:0"
       exe {
@@ -89460,11 +89120,6 @@
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
         '    "metrics_project": "chromium-reclient-metrics"'
@@ -89566,11 +89221,6 @@
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
         '    "metrics_project": "chromium-reclient-metrics"'
@@ -89672,11 +89322,6 @@
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org"'
-        '  },'
         '  "$build/reclient": {'
         '    "instance": "rbe-chromium-untrusted",'
         '    "metrics_project": "chromium-reclient-metrics"'
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 8efe6d3b..334dd62 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -2701,9 +2701,6 @@
     name: "buildbucket/luci.chromium.try/linux-lacros-rel-compilator"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-lacros-tester-rel-reviver"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux-libfuzzer-asan-rel"
   }
   builders {
@@ -8716,11 +8713,6 @@
     category: "default"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/linux-lacros-tester-rel-reviver"
-    category: "default"
-    short_name: "rev"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/Comparison ios (reclient)"
     category: "ios"
     short_name: "cmp"
@@ -8759,11 +8751,6 @@
     short_name: "crs"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/Linux MSan Focal"
-    category: "msan"
-    short_name: "lin"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/linux-upload-perfetto"
     category: "perfetto"
     short_name: "lnx"
@@ -16842,9 +16829,6 @@
     name: "buildbucket/luci.chromium.try/linux-lacros-rel-compilator"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-lacros-tester-rel-reviver"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux-lacros-version-skew-fyi"
   }
   builders {
@@ -16971,9 +16955,6 @@
     name: "buildbucket/luci.chromium.try/linux_chromium_dbg_ng"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux_chromium_msan_focal"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux_chromium_msan_rel_ng"
   }
   builders {
@@ -17268,6 +17249,9 @@
     name: "buildbucket/luci.chromium.reviver/fuchsia-coordinator"
   }
   builders {
+    name: "buildbucket/luci.chromium.reviver/lacros-coordinator"
+  }
+  builders {
     name: "buildbucket/luci.chromium.reviver/runner"
   }
   builder_view_only: true
@@ -17672,9 +17656,6 @@
   builders {
     name: "buildbucket/luci.chromium.try/linux-lacros-rel-compilator"
   }
-  builders {
-    name: "buildbucket/luci.chromium.try/linux-lacros-tester-rel-reviver"
-  }
   builder_view_only: true
 }
 consoles {
@@ -17990,9 +17971,6 @@
     name: "buildbucket/luci.chromium.try/linux_chromium_dbg_ng"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux_chromium_msan_focal"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux_chromium_msan_rel_ng"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 019490a..769dd14 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -1739,15 +1739,6 @@
   }
 }
 job {
-  id: "Linux MSan Focal"
-  realm: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "Linux MSan Focal"
-  }
-}
-job {
   id: "Linux MSan Tests"
   realm: "ci"
   buildbucket {
@@ -4552,6 +4543,16 @@
   }
 }
 job {
+  id: "lacros-coordinator"
+  realm: "reviver"
+  schedule: "0 3,5,7,9 * * *"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "reviver"
+    builder: "lacros-coordinator"
+  }
+}
+job {
   id: "lacros64-archive-rel"
   realm: "ci"
   buildbucket {
@@ -4907,16 +4908,6 @@
   }
 }
 job {
-  id: "linux-lacros-tester-rel-reviver"
-  realm: "ci"
-  schedule: "0 3,5,7,9 * * *"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-lacros-tester-rel-reviver"
-  }
-}
-job {
   id: "linux-lacros-version-skew-fyi"
   realm: "ci"
   buildbucket {
@@ -5928,7 +5919,6 @@
   triggers: "Linux ChromiumOS MSan Focal"
   triggers: "Linux FYI GPU TSAN Release"
   triggers: "Linux MSan Builder"
-  triggers: "Linux MSan Focal"
   triggers: "Linux TSan Builder"
   triggers: "Linux Viz"
   triggers: "MSAN Release (chained origins)"
@@ -6120,7 +6110,6 @@
   triggers: "linux-lacros-code-coverage"
   triggers: "linux-lacros-dbg"
   triggers: "linux-lacros-dbg-fyi"
-  triggers: "linux-lacros-tester-rel-reviver"
   triggers: "linux-lacros-version-skew-fyi"
   triggers: "linux-official"
   triggers: "linux-perfetto-rel"
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index c9f191c..c519380 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -334,36 +334,6 @@
 )
 
 ci.builder(
-    name = "linux-lacros-tester-rel-reviver",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium_no_telemetry_dependencies",
-            apply_configs = [
-                "chromeos",
-            ],
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = [
-                "mb",
-            ],
-            build_config = builder_config.build_config.RELEASE,
-            target_arch = builder_config.target_arch.INTEL,
-            target_bits = 64,
-        ),
-        build_gs_bucket = "chromium-chromiumos-archive",
-    ),
-    console_view_entry = consoles.console_view_entry(
-        category = "default",
-        short_name = "rev",
-    ),
-    os = os.LINUX_DEFAULT,
-    # To avoid peak hours, we run it from 8PM TO 4AM PST. It is
-    # 3 AM to 11 AM UTC.
-    schedule = "0 3,5,7,9 * * *",
-)
-
-ci.builder(
     name = "linux-lacros-version-skew-fyi",
     console_view_entry = consoles.console_view_entry(
         category = "default",
@@ -1957,32 +1927,6 @@
 
 ci.builder(
     # An FYI version of the following builders that runs on Focal:
-    # https://ci.chromium.org/p/chromium/builders/ci/Linux%20MSan%20Builder
-    # https://ci.chromium.org/p/chromium/builders/ci/Linux%20MSan%20Tests
-    # TODO(crbug.com/1260217): Remove this builder when the main MSAN builder
-    # has migrated to focal.
-    name = "Linux MSan Focal",
-    builder_spec = builder_config.builder_spec(
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-        ),
-        chromium_config = builder_config.chromium_config(
-            config = "chromium_msan",
-            apply_configs = ["mb"],
-            build_config = builder_config.build_config.RELEASE,
-        ),
-    ),
-    console_view_entry = consoles.console_view_entry(
-        category = "msan",
-        short_name = "lin",
-    ),
-    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CI,
-    os = os.LINUX_FOCAL,
-    execution_timeout = 16 * time.hour,
-)
-
-ci.builder(
-    # An FYI version of the following builders that runs on Focal:
     # https://ci.chromium.org/p/chromium/builders/ci/Linux%20ChromiumOS%20MSan%20Builder
     # https://ci.chromium.org/p/chromium/builders/ci/Linux%20ChromiumOS%20MSan%20Tests
     # TODO(crbug.com/1260217): Remove this builder when the main MSAN builder
diff --git a/infra/config/subprojects/chromium/ci/chromium.memory.star b/infra/config/subprojects/chromium/ci/chromium.memory.star
index 950bfdba..5b7ad835 100644
--- a/infra/config/subprojects/chromium/ci/chromium.memory.star
+++ b/infra/config/subprojects/chromium/ci/chromium.memory.star
@@ -321,6 +321,7 @@
         short_name = "bld",
     ),
     ssd = True,
+    os = os.LINUX_FOCAL,
 )
 
 linux_memory_builder(
@@ -348,6 +349,7 @@
     ),
     reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CI,
     triggered_by = ["Linux MSan Builder"],
+    os = os.LINUX_FOCAL,
 )
 
 linux_memory_builder(
diff --git a/infra/config/subprojects/chromium/ci/chromium.packager.star b/infra/config/subprojects/chromium/ci/chromium.packager.star
index 45ec91b..15959bae 100644
--- a/infra/config/subprojects/chromium/ci/chromium.packager.star
+++ b/infra/config/subprojects/chromium/ci/chromium.packager.star
@@ -95,6 +95,34 @@
     ),
     executable = "recipe:android/avd_packager",
     properties = {
+        "$build/avd_packager": {
+            "avd_configs": [
+                "tools/android/avd/proto/creation/generic_android19.textpb",
+                "tools/android/avd/proto/creation/generic_android22.textpb",
+                "tools/android/avd/proto/creation/generic_android23.textpb",
+                "tools/android/avd/proto/creation/generic_android24.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android24.textpb",
+                "tools/android/avd/proto/creation/generic_android25.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android25.textpb",
+                "tools/android/avd/proto/creation/generic_android27.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android27.textpb",
+                "tools/android/avd/proto/creation/generic_android28.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android28.textpb",
+                "tools/android/avd/proto/creation/generic_android29.textpb",
+                "tools/android/avd/proto/creation/generic_android30.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android30.textpb",
+                "tools/android/avd/proto/creation/generic_android31.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android31.textpb",
+                "tools/android/avd/proto/creation/generic_android32_foldable.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb",
+                "tools/android/avd/proto/creation/generic_android33.textpb",
+                "tools/android/avd/proto/creation/generic_playstore_android33.textpb",
+            ],
+            "gclient_config": "chromium",
+            "gclient_apply_config": ["android"],
+        },
+        # TODO(crbug.com/1381614): Remove properties below after
+        # avd_packager is refactored to recipe_module in crrev.com/c/4021719
         "avd_configs": [
             "tools/android/avd/proto/creation/generic_android19.textpb",
             "tools/android/avd/proto/creation/generic_android22.textpb",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
index f9ab8c6..6268c27 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
@@ -204,15 +204,6 @@
 )
 
 try_.builder(
-    name = "linux-lacros-tester-rel-reviver",
-    mirrors = [
-        "ci/linux-lacros-tester-rel-reviver",
-    ],
-    builderless = True,
-    main_list_view = "try",
-)
-
-try_.builder(
     name = "chromeos-jacuzzi-rel",
     branch_selector = branches.CROS_LTS_MILESTONE,
     mirrors = [
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star b/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
index 2dd0121f..fda343c 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
@@ -195,6 +195,7 @@
     test_presentation = resultdb.test_presentation(
         grouping_keys = ["status", "v.test_suite", "v.gpu"],
     ),
+    goma_backend = None,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 4371483..cb14c6f3 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -22,7 +22,7 @@
     os = os.LINUX_DEFAULT,
     pool = try_.DEFAULT_POOL,
     reclient_instance = reclient.instance.DEFAULT_UNTRUSTED,
-    reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CQ,
+    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
     compilator_reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
     service_account = try_.DEFAULT_SERVICE_ACCOUNT,
 
@@ -49,6 +49,7 @@
     mirrors = [
         "ci/fuchsia-arm64-cast-receiver-rel",
     ],
+    goma_backend = None,
 )
 
 try_.builder(
@@ -102,6 +103,7 @@
         },
     },
     tryjob = try_.job(),
+    goma_backend = None,
 )
 
 try_.builder(
@@ -120,21 +122,25 @@
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
+    goma_backend = None,
 )
 
 try_.builder(
     name = "fuchsia-deterministic-dbg",
     executable = "recipe:swarming/deterministic_build",
+    goma_backend = None,
 )
 
 try_.builder(
     name = "fuchsia-fyi-arm64-dbg",
     mirrors = ["ci/fuchsia-fyi-arm64-dbg"],
+    goma_backend = None,
 )
 
 try_.builder(
     name = "fuchsia-fyi-x64-dbg",
     mirrors = ["ci/fuchsia-fyi-x64-dbg"],
+    goma_backend = None,
 )
 
 try_.orchestrator_builder(
@@ -159,6 +165,7 @@
     cores = 16,
     ssd = True,
     main_list_view = "try",
+    goma_backend = None,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index fe28d37c..f0762a5 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -522,17 +522,6 @@
 )
 
 try_.builder(
-    name = "linux_chromium_msan_focal",
-    mirrors = [
-        "ci/Linux MSan Focal",
-    ],
-    execution_timeout = 16 * time.hour,
-    goma_backend = None,
-    os = os.LINUX_FOCAL,
-    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
-)
-
-try_.builder(
     name = "linux_chromium_msan_rel_ng",
     mirrors = [
         "ci/Linux MSan Builder",
@@ -540,6 +529,7 @@
     ],
     execution_timeout = 6 * time.hour,
     goma_backend = None,
+    os = os.LINUX_FOCAL,
     reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
 )
 
@@ -681,16 +671,19 @@
 try_.builder(
     name = "tricium-metrics-analysis",
     executable = "recipe:tricium_metrics",
+    goma_backend = None,
 )
 
 try_.builder(
     name = "tricium-oilpan-analysis",
     executable = "recipe:tricium_oilpan",
+    goma_backend = None,
 )
 
 try_.builder(
     name = "tricium-simple",
     executable = "recipe:tricium_simple",
+    goma_backend = None,
 )
 
 try_.gpu.optional_tests_builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.star b/infra/config/subprojects/chromium/try/tryserver.chromium.star
index f51fbc60..205b56c 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.star
@@ -45,6 +45,9 @@
     mirrors = [
         "ci/fuchsia-official",
     ],
+    goma_backend = None,
+    reclient_instance = reclient.instance.DEFAULT_UNTRUSTED,
+    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.tricium.star b/infra/config/subprojects/chromium/try/tryserver.chromium.tricium.star
index 379121c1..1ef9ff8 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.tricium.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.tricium.star
@@ -61,6 +61,7 @@
     name = "fuchsia-clang-tidy-rel",
     executable = "recipe:tricium_clang_tidy_wrapper",
     os = os.LINUX_DEFAULT,
+    goma_backend = None,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/reviver/reviver.star b/infra/config/subprojects/reviver/reviver.star
index 10a65fe..0b53afc 100644
--- a/infra/config/subprojects/reviver/reviver.star
+++ b/infra/config/subprojects/reviver/reviver.star
@@ -73,6 +73,20 @@
     schedule = "0 1,3,5,7,9,11,13 * * *",
 )
 
+# A coordinator for lacros.
+polymorphic.launcher(
+    name = "lacros-coordinator",
+    runner = "reviver/runner",
+    target_builders = [
+        "ci/linux-lacros-tester-rel",
+    ],
+    os = os.LINUX_DEFAULT,
+    pool = ci.DEFAULT_POOL,
+    # To avoid peak hours, we run it from 8PM TO 4AM PST. It is
+    # 3 AM to 11 AM UTC.
+    schedule = "0 3,5,7,9 * * *",
+)
+
 builder(
     name = "runner",
     executable = "recipe:reviver/chromium/runner",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_destination_view.swift b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_destination_view.swift
index d6fc001..fb3992a 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_destination_view.swift
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_destination_view.swift
@@ -164,7 +164,7 @@
             )
             // Pad the color circle by 0.5, otherwise the color shows up faintly
             // around the border.
-            .background(Circle().foregroundColor(.blue500).padding(0.5))
+            .background(Circle().foregroundColor(.blue600).padding(0.5))
             .frame(width: Dimensions.badgeWidth, height: Dimensions.badgeWidth)
             .offset(x: Dimensions.iconWidth / 2, y: -Dimensions.iconWidth / 2)
         } else if destination.badge == .newLabel {
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index f45fc48..d3e1dbc 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-1fab80f8d30a950d916d2f5fa7a9ee4598ba51bb
\ No newline at end of file
+58bc21bb40977a250250443b23534d0f32684d85
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index 71a3ad7..08c56923 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-c54f66a553042d785c1332646ad43ba4be4b093d
\ No newline at end of file
+9ac10e93fbb498550f647e0e12665c35a1394008
\ No newline at end of file
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 dcb2734..7f55562 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 @@
-62d2991bec0bc791de1eda733bdaf9bc85810ca5
\ No newline at end of file
+0528fd7b028dbd915238625ac3aa561248a674a8
\ 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 c8f4cf3..59d4403 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 @@
-5ac80fd19444d1ab46105a6cb6c6488c118c2ae1
\ No newline at end of file
+f721009610b3ab78cecc2f28c63e920cddae0609
\ 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 98325356..790268c 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 @@
-2a312d62c79ba6bff052798a08c0df65897e7010
\ No newline at end of file
+87994f69c51e206c051261c3181ebec3cd7b3b4d
\ 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 e272dada..b767ab4 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 @@
-5815f5e6e7b498bf9811991cdf1f97ec87acc7db
\ No newline at end of file
+64785d4bf68a9326ea0c8a8591ba3beda0ef3c78
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 34e531d..6d429c4b 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-26ca56d0f0cce70fb8fb67273d092092260f0914
\ No newline at end of file
+d9a62341e0a244866311ffe9967baef2e67d5f0d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index b938421..68e0e0f 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-da7e589cc79803c9be82bf91dd7875a945c4d4bc
\ No newline at end of file
+6115887860f00219f1ce4783e13989a0b304a19e
\ 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 90406c7..348c7c1 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 @@
-922056ab8f76f6d76865f4632fa697658644dabb
\ No newline at end of file
+cc3a0454ec48d95fc502b00044cd06dc89bf30c6
\ 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 de71285..9133d8d72 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 @@
-af452f809a5e12711874d6f769f00625bfaf0325
\ No newline at end of file
+bb3cfc5448bc3e6b895504e9f878982c2faa0e6b
\ 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 c8740495..f04ef32 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 @@
-80e9873604bb0a97afda669b59356f28f519f4f7
\ No newline at end of file
+a44f465bfd44091eba2f32a05568e08fac5752f4
\ 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 f25bb18..0238282 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 @@
-5d1b7d2f1735fd23b20c40f798c392c4a1eee5b4
\ No newline at end of file
+6fa84f8efa31468f084a5800f976106801640140
\ No newline at end of file
diff --git a/ios/third_party/webkit/BUILD.gn b/ios/third_party/webkit/BUILD.gn
index fa606621..9ecbe26 100644
--- a/ios/third_party/webkit/BUILD.gn
+++ b/ios/third_party/webkit/BUILD.gn
@@ -183,14 +183,14 @@
       "--output_dir",
       rebase_path("$_webkit_mac_out_base_dir"),
 
-      # The arguments below allow building with Xcode 12.5 (with the macOS 11.3
-      # SDK).
-      "MACOSX_DEPLOYMENT_TARGET=11.3",
-      "TARGET_MAC_OS_X_VERSION_MAJOR=110000",
-      "MAC_OS_X_VERSION_ACTUAL=110300",
-      "MAC_OS_X_VERSION_MAJOR=110000",
-      "MAC_OS_X_VERSION_MINOR=110300",
-      "MAC_OS_X_PRODUCT_BUILD_VERSION=20E232",
+      # The arguments below allow building with Xcode 13.4 (with the macOS 12.3
+      # SDK) on macOS 12.6.1.
+      "MACOSX_DEPLOYMENT_TARGET=12.3",
+      "TARGET_MAC_OS_X_VERSION_MAJOR=120000",
+      "MAC_OS_X_VERSION_ACTUAL=120601",
+      "MAC_OS_X_VERSION_MAJOR=120000",
+      "MAC_OS_X_VERSION_MINOR=120600",
+      "MAC_OS_X_PRODUCT_BUILD_VERSION=21G217",
     ]
   }
 
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 37d99a63..3926010 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -72,7 +72,6 @@
   "public/cwv_password.h",
   "public/cwv_preferences.h",
   "public/cwv_preview_element_info.h",
-  "public/cwv_script_command.h",
   "public/cwv_ssl_error_handler.h",
   "public/cwv_ssl_status.h",
   "public/cwv_sync_controller.h",
@@ -153,8 +152,6 @@
     "internal/cwv_preferences_internal.h",
     "internal/cwv_preview_element_info.mm",
     "internal/cwv_preview_element_info_internal.h",
-    "internal/cwv_script_command.mm",
-    "internal/cwv_script_command_internal.h",
     "internal/cwv_ssl_error_handler.mm",
     "internal/cwv_ssl_error_handler_internal.h",
     "internal/cwv_ssl_status.mm",
diff --git a/ios/web_view/internal/cwv_script_command.mm b/ios/web_view/internal/cwv_script_command.mm
deleted file mode 100644
index e68e37242..0000000
--- a/ios/web_view/internal/cwv_script_command.mm
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web_view/public/cwv_script_command.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation CWVScriptCommand
-
-@synthesize content = _content;
-@synthesize mainDocumentURL = _mainDocumentURL;
-@synthesize userInteracting = _userInteracting;
-
-- (instancetype)initWithContent:(nullable NSDictionary*)content
-                mainDocumentURL:(NSURL*)mainDocumentURL
-                userInteracting:(BOOL)userInteracting {
-  self = [super init];
-  if (self) {
-    _content = [content copy];
-    _mainDocumentURL = [mainDocumentURL copy];
-    _userInteracting = userInteracting;
-  }
-  return self;
-}
-
-@end
diff --git a/ios/web_view/internal/cwv_script_command_internal.h b/ios/web_view/internal/cwv_script_command_internal.h
deleted file mode 100644
index 449af36..0000000
--- a/ios/web_view/internal/cwv_script_command_internal.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
-#define IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
-
-#import "ios/web_view/public/cwv_script_command.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface CWVScriptCommand ()
-
-/**
- * Designated initializer.
- *
- * @param content Content of the command. nil in case of an error converting the
- *     content object.
- * @param mainDocumentURL URL of the document in the main web view frame.
- * @param userInteracting YES if the user is currently interacting with the
- *     page.
- */
-- (instancetype)initWithContent:(nullable NSDictionary*)content
-                mainDocumentURL:(NSURL*)mainDocumentURL
-                userInteracting:(BOOL)userInteracting NS_DESIGNATED_INITIALIZER;
-
-@end
-
-NS_ASSUME_NONNULL_END
-
-#endif  // IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index 8ddfaae8..acb7ae08 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -52,7 +52,6 @@
 #import "ios/web_view/internal/cwv_favicon_internal.h"
 #import "ios/web_view/internal/cwv_html_element_internal.h"
 #import "ios/web_view/internal/cwv_navigation_action_internal.h"
-#import "ios/web_view/internal/cwv_script_command_internal.h"
 #import "ios/web_view/internal/cwv_ssl_status_internal.h"
 #import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
 #import "ios/web_view/internal/language/web_view_url_language_histogram_factory.h"
@@ -123,16 +122,6 @@
   return nil;
 }
 
-// Converts base::Value expected to be a dictionary to NSDictionary.
-NSDictionary* NSDictionaryFromDictionaryValue(const base::Value& value) {
-  DCHECK(value.is_dict()) << "Incorrect value type: " << value.type();
-
-  NSDictionary* ns_dictionary = base::mac::ObjCCastStrict<NSDictionary>(
-      NSObjectFromCollectionValue(&value));
-  DCHECK(ns_dictionary) << "Failed to convert JSON to NSDictionary";
-  return ns_dictionary;
-}
-
 // Converts base::Value::Dict to NSDictionary.
 NSDictionary* NSDictionaryFromDictValue(const base::Value::Dict& value) {
   std::string json;
@@ -176,11 +165,6 @@
   // Handles presentation of JavaScript dialogs.
   std::unique_ptr<ios_web_view::WebViewJavaScriptDialogPresenter>
       _javaScriptDialogPresenter;
-  // Stores the script command callbacks with subscriptions.
-  std::unordered_map<std::string,
-                     std::pair<web::WebState::ScriptCommandCallback,
-                               base::CallbackListSubscription>>
-      _scriptCommandCallbacks;
   CRWSessionStorage* _cachedSessionStorage;
 }
 
@@ -391,12 +375,6 @@
 }
 
 - (void)evaluateJavaScript:(NSString*)javaScriptString
-         completionHandler:(void (^)(id, NSError*))completionHandler {
-  [_webState->GetJSInjectionReceiver() executeJavaScript:javaScriptString
-                                       completionHandler:completionHandler];
-}
-
-- (void)evaluateJavaScript:(NSString*)javaScriptString
                 completion:(void (^)(id, NSError*))completion {
   web::WebFrame* mainFrame = web::GetMainFrame(_webState.get());
   if (!mainFrame) {
@@ -647,34 +625,6 @@
   }
 }
 
-- (void)addScriptCommandHandler:(id<CWVScriptCommandHandler>)handler
-                  commandPrefix:(NSString*)commandPrefix {
-  CWVWebView* __weak weakSelf = self;
-  const web::WebState::ScriptCommandCallback callback = base::BindRepeating(
-      ^(const base::Value& content, const GURL& mainDocumentURL,
-        bool userInteracting, web::WebFrame* senderFrame) {
-        NSDictionary* nsContent = NSDictionaryFromDictionaryValue(content);
-        CWVScriptCommand* command = [[CWVScriptCommand alloc]
-            initWithContent:nsContent
-            mainDocumentURL:net::NSURLWithGURL(mainDocumentURL)
-            userInteracting:userInteracting];
-        [handler webView:weakSelf
-            handleScriptCommand:command
-                  fromMainFrame:senderFrame->IsMainFrame()];
-      });
-
-  std::string stdCommandPrefix = base::SysNSStringToUTF8(commandPrefix);
-  auto subscription =
-      _webState->AddScriptCommandCallback(callback, stdCommandPrefix);
-  _scriptCommandCallbacks[stdCommandPrefix] = {callback,
-                                               std::move(subscription)};
-}
-
-- (void)removeScriptCommandHandlerForCommandPrefix:(NSString*)commandPrefix {
-  std::string stdCommandPrefix = base::SysNSStringToUTF8(commandPrefix);
-  _scriptCommandCallbacks.erase(stdCommandPrefix);
-}
-
 - (void)addMessageHandler:(void (^)(NSDictionary* payload))handler
                forCommand:(NSString*)nsCommand {
   DCHECK(handler);
@@ -885,11 +835,6 @@
       std::make_unique<ios_web_view::WebViewJavaScriptDialogPresenter>(self,
                                                                        nullptr);
 
-  for (auto& pair : _scriptCommandCallbacks) {
-    pair.second.second =
-        _webState->AddScriptCommandCallback(pair.second.first, pair.first);
-  }
-
   _webState->GetWebViewProxy().allowsBackForwardNavigationGestures =
       allowsBackForwardNavigationGestures;
 
diff --git a/ios/web_view/public/cwv_script_command.h b/ios/web_view/public/cwv_script_command.h
deleted file mode 100644
index 10a278f..0000000
--- a/ios/web_view/public/cwv_script_command.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
-#define IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
-
-#import <Foundation/Foundation.h>
-
-#import "cwv_export.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class CWVWebView;
-
-// A script command passed to CWVScriptCommandHandler.
-CWV_EXPORT
-@interface CWVScriptCommand : NSObject
-
-- (instancetype)init NS_UNAVAILABLE;
-
-// Content of the command. nil in case of an error converting the content
-// object.
-@property(nonatomic, readonly, nullable, copy) NSDictionary* content;
-
-// URL of the document in the main web view frame.
-@property(nonatomic, readonly, copy) NSURL* mainDocumentURL;
-
-// YES if the user is currently interacting with the page.
-@property(nonatomic, readonly, getter=isUserInteracting) BOOL userInteracting;
-
-@end
-
-// Provides a method for receiving commands from JavaScript running in a web
-// page.
-@protocol CWVScriptCommandHandler<NSObject>
-
-- (BOOL)webView:(CWVWebView*)webView
-    handleScriptCommand:(CWVScriptCommand*)command
-          fromMainFrame:(BOOL)fromMainFrame;
-
-@end
-
-NS_ASSUME_NONNULL_END
-
-#endif  // IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
diff --git a/ios/web_view/public/cwv_web_view.h b/ios/web_view/public/cwv_web_view.h
index 7f79f39..0877d95 100644
--- a/ios/web_view/public/cwv_web_view.h
+++ b/ios/web_view/public/cwv_web_view.h
@@ -19,7 +19,6 @@
 @class CWVTranslationController;
 @class CWVWebViewConfiguration;
 @protocol CWVNavigationDelegate;
-@protocol CWVScriptCommandHandler;
 @protocol CWVUIDelegate;
 @class CWVSSLStatus;
 
@@ -218,38 +217,6 @@
 // Unlike WKWebView, this method supports HTTPBody.
 - (void)loadRequest:(NSURLRequest*)request;
 
-// Evaluates a JavaScript string.
-// The completion handler is invoked when script evaluation completes.
-//
-// Note that |javaScriptString| is wrapped with:
-//   if (<implementation defined>) { ... }
-// before evaluation, which causes some tricky side effect when you use |let| or
-// |const| in the script.
-//
-//   1. Variables defined with |let| or |const| at the top level of the script
-//      do NOT become a global variable. i.e., It is accessible neither from
-//      scripts in the page nor another call to
-//      -evaluateJavaScript:completionHandler:. Variables defined with |var|
-//      DOES become a global variable.
-//
-//   2. Variables defined with |let| or |const| at the top level are not
-//      accessible from top level functions, even in the same script. Variable
-//      defined with |var| doesn't have this issue either. e.g., evaluation of
-//      this script causes an error:
-//
-//        let a =  3;
-//        function f() {
-//          console.log(a);  // ReferenceError: Can't find variable: a
-//        }
-//        f();
-//
-// To workaround the issue, you can use |var| instead, or an explicit reference
-// to window.xxx. This is because |let| and |const| are scoped by braces while
-// |var| isn't, and due to tricky behavior of WebKit in non-strict mode.
-// DEPRECATED. Use `evaluateJavaScript:completion:` instead.
-- (void)evaluateJavaScript:(NSString*)javaScriptString
-         completionHandler:(void (^)(id, NSError*))completionHandler;
-
 // Evaluates a JavaScript string in the main frame of the page content world.
 // `completion` is invoked with the result of evaluating the script and a
 // boolean representing success (`YES`) or failure (`NO`) of the evaluation.
@@ -261,29 +228,6 @@
 - (void)evaluateJavaScript:(NSString*)javaScriptString
                 completion:(void (^)(id result, NSError* error))completion;
 
-// Registers a handler that will be called when a command matching
-// |commandPrefix| is received.
-//
-// Web pages can send a command by executing JavaScript like this:
-//   __gCrWeb.message.invokeOnHost(
-//       {'command': 'test.command1', 'key1':'value1', 'key2': 42});
-// And receive it by:
-//   [webView addScriptCommandHandler:handler commandPrefix:@"test"];
-//
-// Make sure to call -removeScriptCommandHandlerForCommandPrefix: with the same
-// prefix before deallocating a CWVWebView instance. Otherwise it causes an
-// assertion failure.
-//
-// This provides a similar functionarity to -[WKUserContentController
-// addScriptMessageHandler:name:].
-// DEPRECATED: Use `addMessageHandler:forCommand:` instead.
-- (void)addScriptCommandHandler:(id<CWVScriptCommandHandler>)handler
-                  commandPrefix:(NSString*)commandPrefix;
-
-// Removes the handler associated with |commandPrefix|.
-// DEPRECATED: Use `removeMessageHandlerForCommand:` instead.
-- (void)removeScriptCommandHandlerForCommandPrefix:(NSString*)commandPrefix;
-
 // Adds a message handler for messages sent from JavaScript.
 // `handler` will be called each time a message is sent with the corresponding
 // value of `command`. To send messages from JavaScript, use the WebKit
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index 8818d3f..ef6d621 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -28,7 +28,6 @@
                                    CWVLeakCheckServiceObserver,
                                    CWVNavigationDelegate,
                                    CWVUIDelegate,
-                                   CWVScriptCommandHandler,
                                    CWVSyncControllerDelegate,
                                    UIScrollViewDelegate,
                                    UITextFieldDelegate>
@@ -1020,8 +1019,6 @@
                        NSKeyValueObservingOptionInitial
                context:nil];
 
-  [webView addScriptCommandHandler:self commandPrefix:@"test"];
-
   [webView
       addMessageHandler:^(NSDictionary* payload) {
         NSLog(@"message handler payload received =\n%@", payload);
@@ -1036,7 +1033,6 @@
   [_webView removeObserver:self forKeyPath:@"canGoBack"];
   [_webView removeObserver:self forKeyPath:@"canGoForward"];
   [_webView removeObserver:self forKeyPath:@"loading"];
-  [_webView removeScriptCommandHandlerForCommandPrefix:@"test"];
   [_webView removeMessageHandlerForCommand:@"messageHandlerCommand"];
 
   _webView = nil;
@@ -1046,7 +1042,6 @@
   [_webView removeObserver:self forKeyPath:@"canGoBack"];
   [_webView removeObserver:self forKeyPath:@"canGoForward"];
   [_webView removeObserver:self forKeyPath:@"loading"];
-  [_webView removeScriptCommandHandlerForCommandPrefix:@"test"];
   [_webView removeMessageHandlerForCommand:@"messageHandlerCommand"];
 }
 
@@ -1421,15 +1416,6 @@
   [task startDownloadToLocalFileAtPath:self.downloadFilePath];
 }
 
-#pragma mark CWVScriptCommandHandler
-
-- (BOOL)webView:(CWVWebView*)webView
-    handleScriptCommand:(nonnull CWVScriptCommand*)command
-          fromMainFrame:(BOOL)fromMainFrame {
-  NSLog(@"%@ command.content=%@", NSStringFromSelector(_cmd), command.content);
-  return YES;
-}
-
 #pragma mark CWVAutofillDataManagerObserver
 
 - (void)autofillDataManagerDataDidChange:
diff --git a/ios/web_view/test/BUILD.gn b/ios/web_view/test/BUILD.gn
index 27fbd26..72a88edd 100644
--- a/ios/web_view/test/BUILD.gn
+++ b/ios/web_view/test/BUILD.gn
@@ -28,7 +28,6 @@
       "web_view_inttest_base.mm",
       "web_view_kvo_inttest.mm",
       "web_view_restorable_state_inttest.mm",
-      "web_view_script_command_inttest.mm",
       "web_view_script_message_handler_inttest.mm",
     ]
   }
diff --git a/ios/web_view/test/web_view_script_command_inttest.mm b/ios/web_view/test/web_view_script_command_inttest.mm
deleted file mode 100644
index b2d07fd14..0000000
--- a/ios/web_view/test/web_view_script_command_inttest.mm
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <ChromeWebView/ChromeWebView.h>
-#import <Foundation/Foundation.h>
-
-#import "base/test/ios/wait_util.h"
-#import "ios/web_view/test/web_view_inttest_base.h"
-#import "ios/web_view/test/web_view_test_util.h"
-#import "net/base/mac/url_conversions.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "testing/gtest_mac.h"
-#include "url/gurl.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface CWVFakeScriptCommandHandler : NSObject<CWVScriptCommandHandler>
-
-@property(nonatomic) CWVScriptCommand* lastReceivedCommand;
-
-- (BOOL)webView:(CWVWebView*)webView
-    handleScriptCommand:(CWVScriptCommand*)command
-          fromMainFrame:(BOOL)fromMainFrame;
-
-@end
-
-@implementation CWVFakeScriptCommandHandler
-
-@synthesize lastReceivedCommand = _lastReceivedCommand;
-
-- (BOOL)webView:(CWVWebView*)webView
-    handleScriptCommand:(CWVScriptCommand*)command
-          fromMainFrame:(BOOL)fromMainFrame {
-  self.lastReceivedCommand = command;
-  return YES;
-}
-
-@end
-
-namespace ios_web_view {
-
-// Tests the script command feature in CWVWebView.
-using WebViewScriptCommandTest = WebViewInttestBase;
-
-// Tests that a handler added by -[CWVWebView
-// addScriptCommandHandler:commandPrefix] is invoked by JavaScript.
-TEST_F(WebViewScriptCommandTest, TestScriptCommand) {
-  ASSERT_TRUE(test_server_->Start());
-  CWVFakeScriptCommandHandler* handler =
-      [[CWVFakeScriptCommandHandler alloc] init];
-  [web_view_ addScriptCommandHandler:handler commandPrefix:@"test"];
-
-  // Uses GetUrlForPageWithHtmlBody() instead of simply using about:blank
-  // because it looks __gCrWeb may not be available on about:blank.
-  // TODO(crbug.com/836114): Analyze why.
-  NSURL* url = net::NSURLWithGURL(GetUrlForPageWithHtmlBody(""));
-  ASSERT_TRUE(test::LoadUrl(web_view_, url));
-  ASSERT_TRUE(test::WaitForWebViewLoadCompletionOrTimeout(web_view_));
-
-  NSString* script =
-      @"__gCrWeb.message.invokeOnHost("
-      @"{'command': 'test.command1', 'key1': 'value1', 'key2': 42});";
-  NSError* error;
-  test::EvaluateJavaScript(web_view_, script, &error);
-  EXPECT_FALSE(error);
-
-  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
-      base::test::ios::kWaitForJSCompletionTimeout, ^{
-        return handler.lastReceivedCommand != nil;
-      }));
-
-  EXPECT_NSEQ(@"test.command1",
-              handler.lastReceivedCommand.content[@"command"]);
-  EXPECT_NSEQ(@"value1", handler.lastReceivedCommand.content[@"key1"]);
-  EXPECT_NSEQ(@42, handler.lastReceivedCommand.content[@"key2"]);
-  EXPECT_NSEQ(url, handler.lastReceivedCommand.mainDocumentURL);
-  EXPECT_FALSE(handler.lastReceivedCommand.userInteracting);
-
-  [web_view_ removeScriptCommandHandlerForCommandPrefix:@"test"];
-}
-
-// Tests that added script commands are still valid after state restoration.
-// Tests the same thing as TestScriptCommand() after state restoration.
-TEST_F(WebViewScriptCommandTest, TestScriptCommandAfterStateRestoration) {
-  ASSERT_TRUE(test_server_->Start());
-  CWVFakeScriptCommandHandler* handler =
-      [[CWVFakeScriptCommandHandler alloc] init];
-  [web_view_ addScriptCommandHandler:handler commandPrefix:@"test"];
-
-  CWVWebView* source_web_view = test::CreateWebView();
-  test::CopyWebViewState(source_web_view, web_view_);
-
-  // Uses GetUrlForPageWithHtmlBody() instead of simply using about:blank
-  // because it looks __gCrWeb may not be available on about:blank.
-  // TODO(crbug.com/836114): Analyze why.
-  NSURL* url = net::NSURLWithGURL(GetUrlForPageWithHtmlBody(""));
-  ASSERT_TRUE(test::LoadUrl(web_view_, url));
-  ASSERT_TRUE(test::WaitForWebViewLoadCompletionOrTimeout(web_view_));
-
-  NSString* script =
-      @"__gCrWeb.message.invokeOnHost("
-      @"{'command': 'test.command1', 'key1': 'value1', 'key2': 42});";
-  NSError* error;
-  test::EvaluateJavaScript(web_view_, script, &error);
-  ASSERT_FALSE(error);
-
-  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
-      base::test::ios::kWaitForJSCompletionTimeout, ^{
-        return handler.lastReceivedCommand != nil;
-      }));
-
-  EXPECT_NSEQ(@"test.command1",
-              handler.lastReceivedCommand.content[@"command"]);
-  EXPECT_NSEQ(@"value1", handler.lastReceivedCommand.content[@"key1"]);
-  EXPECT_NSEQ(@42, handler.lastReceivedCommand.content[@"key2"]);
-  EXPECT_NSEQ(url, handler.lastReceivedCommand.mainDocumentURL);
-  EXPECT_FALSE(handler.lastReceivedCommand.userInteracting);
-
-  [web_view_ removeScriptCommandHandlerForCommandPrefix:@"test"];
-}
-
-}  // namespace ios_web_view
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index 7dcd29a..8e473fe 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -361,7 +361,7 @@
       "av1_picture.cc",
       "av1_picture.h",
     ]
-    public_deps += [ "//third_party/libgav1:libgav1" ]
+    public_deps += [ "//third_party/libgav1:libgav1_parser" ]
   }
 }
 
diff --git a/media/gpu/v4l2/BUILD.gn b/media/gpu/v4l2/BUILD.gn
index 71c7d5d..cfc2d8c 100644
--- a/media/gpu/v4l2/BUILD.gn
+++ b/media/gpu/v4l2/BUILD.gn
@@ -125,9 +125,8 @@
     ]
   }
 
-  # TODO(b/243970152): update use_libgav1_parser flag
   if (use_libgav1_parser) {
-    deps += [ "//third_party/libgav1:libgav1" ]
+    deps += [ "//third_party/libgav1:libgav1_parser" ]
   }
 }
 
@@ -194,6 +193,6 @@
   ]
 
   if (use_libgav1_parser) {
-    deps += [ "//third_party/libgav1:libgav1" ]
+    deps += [ "//third_party/libgav1:libgav1_parser" ]
   }
 }
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index 29aa09b..924f778 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -414,7 +414,7 @@
     "//media:test_support",
     "//media/gpu:common",
     "//media/parsers:parsers",
-    "//third_party/libgav1:libgav1",
+    "//third_party/libgav1:libgav1_parser",
     "//third_party/libyuv",
     "//ui/gfx/codec:codec",
     "//ui/gfx/geometry",
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 5ec4b61..3e4d9e3 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -315,6 +315,7 @@
   }
 
   aspect_ratio_ = config.aspect_ratio();
+  is_rtc_ = config.is_rtc();
 
   output_cb_ = std::move(output_cb);
   waiting_cb_ = std::move(waiting_cb);
@@ -1110,8 +1111,22 @@
         encryption_scheme_);
     decoder_delegate_ = accelerator.get();
 
-    decoder_ = std::make_unique<VP9Decoder>(std::move(accelerator), profile_,
-                                            color_space_);
+    // The VaapiVideoDecoder can generally deal with larger-to-smaller VP9
+    // resolution changes without re-configuring the VA-API context. However, we
+    // exclude two use cases:
+    //
+    // - WebRTC: resolution changes are only expected to occur on key frames.
+    //   Therefore, if we ignore larger-to-smaller changes, we would be
+    //   introducing a memory usage regression on the common case.
+    //
+    // - Protected content: the scaling logic in
+    //   ApplyResolutionChangeWithScreenSizes() assumes that we have a fresh
+    //   picture size so that we can calculate the correct scaling factor.
+    const bool ignore_resolution_changes_to_smaller =
+        !(cdm_context_ref_ && !transcryption_) && !is_rtc_;
+    decoder_ = std::make_unique<VP9Decoder>(
+        std::move(accelerator), profile_, color_space_,
+        ignore_resolution_changes_to_smaller);
   }
 #if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
   else if (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX) {
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h
index c76b2c5d..26f279a 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.h
+++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -217,6 +217,7 @@
   VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
   VideoColorSpace color_space_;
   absl::optional<gfx::HDRMetadata> hdr_metadata_;
+  bool is_rtc_;
 
   // Aspect ratio from the config.
   VideoAspectRatio aspect_ratio_;
diff --git a/media/gpu/vp9_decoder.cc b/media/gpu/vp9_decoder.cc
index ca87587c..a2cf762 100644
--- a/media/gpu/vp9_decoder.cc
+++ b/media/gpu/vp9_decoder.cc
@@ -113,9 +113,12 @@
 
 VP9Decoder::VP9Decoder(std::unique_ptr<VP9Accelerator> accelerator,
                        VideoCodecProfile profile,
-                       const VideoColorSpace& container_color_space)
+                       const VideoColorSpace& container_color_space,
+                       bool ignore_resolution_changes_to_smaller)
     : state_(kNeedStreamMetadata),
       container_color_space_(container_color_space),
+      ignore_resolution_changes_to_smaller_(
+          ignore_resolution_changes_to_smaller),
       // TODO(hiroh): Set profile to UNKNOWN.
       profile_(profile),
       accelerator_(std::move(accelerator)),
@@ -291,11 +294,19 @@
     }
 
     DCHECK(!new_pic_size.IsEmpty());
-    if (new_pic_size != pic_size_ || new_profile != profile_ ||
-        curr_frame_hdr_->bit_depth != bit_depth_) {
+
+    const bool is_pic_size_different = new_pic_size != pic_size_;
+    const bool is_pic_size_larger = new_pic_size.width() > pic_size_.width() ||
+                                    new_pic_size.height() > pic_size_.height();
+    const bool is_new_configuration_different_enough =
+        (ignore_resolution_changes_to_smaller_ ? is_pic_size_larger
+                                               : is_pic_size_different) ||
+        new_profile != profile_ || curr_frame_hdr_->bit_depth != bit_depth_;
+
+    if (is_new_configuration_different_enough) {
       DVLOG(1) << "New profile: " << GetProfileName(new_profile)
-               << ", New resolution: " << new_pic_size.ToString()
-               << ", New bit depth: "
+               << ", new resolution: " << new_pic_size.ToString()
+               << ", new bit depth: "
                << base::strict_cast<int>(curr_frame_hdr_->bit_depth);
 
       if (!curr_frame_hdr_->IsKeyframe() &&
diff --git a/media/gpu/vp9_decoder.h b/media/gpu/vp9_decoder.h
index 89fff46..4067123678 100644
--- a/media/gpu/vp9_decoder.h
+++ b/media/gpu/vp9_decoder.h
@@ -119,7 +119,8 @@
   explicit VP9Decoder(
       std::unique_ptr<VP9Accelerator> accelerator,
       VideoCodecProfile profile,
-      const VideoColorSpace& container_color_space = VideoColorSpace());
+      const VideoColorSpace& container_color_space = VideoColorSpace(),
+      bool ignore_resolution_changes_to_smaller = false);
 
   VP9Decoder(const VP9Decoder&) = delete;
   VP9Decoder& operator=(const VP9Decoder&) = delete;
@@ -177,6 +178,11 @@
   // Color space provided by the container.
   const VideoColorSpace container_color_space_;
 
+  // Many implementors (e.g. Intel and AMD via VA-API) will support changes of
+  // resolution to smaller without reconfiguring the driver (e.g. keeping the
+  // reference frames etc), but others won't.
+  const bool ignore_resolution_changes_to_smaller_ = false;
+
   // Reference frames currently in use.
   Vp9ReferenceFrameVector ref_frames_;
 
diff --git a/net/cert/pki/path_builder_unittest.cc b/net/cert/pki/path_builder_unittest.cc
index d333db81..5366801 100644
--- a/net/cert/pki/path_builder_unittest.cc
+++ b/net/cert/pki/path_builder_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "net/cert/pki/path_builder.h"
 
+#include <algorithm>
 #include "base/base_paths.h"
 #include "base/callback_forward.h"
 #include "base/containers/span.h"
@@ -957,10 +958,9 @@
   EXPECT_TRUE(AreCertsEq(e_by_e_, path.certs[2]));
 
   // Should only be one valid path, the one above.
-  int valid_paths = 0;
-  for (const auto& candidate_path : result.paths) {
-    valid_paths += candidate_path->IsValid() ? 1 : 0;
-  }
+  const int valid_paths = std::count_if(
+      result.paths.begin(), result.paths.end(),
+      [](const auto& candidate_path) { return candidate_path->IsValid(); });
   ASSERT_EQ(1, valid_paths);
 }
 
diff --git a/net/spdy/fuzzing/hpack_fuzz_util.cc b/net/spdy/fuzzing/hpack_fuzz_util.cc
index d72aff8..7de999de 100644
--- a/net/spdy/fuzzing/hpack_fuzz_util.cc
+++ b/net/spdy/fuzzing/hpack_fuzz_util.cc
@@ -102,7 +102,8 @@
 
 // static
 size_t HpackFuzzUtil::SampleExponential(size_t mean, size_t sanity_bound) {
-  return std::min(static_cast<size_t>(-std::log(base::RandDouble()) * mean),
+  // Use `1-base::RandDouble()` to avoid log(0).
+  return std::min(static_cast<size_t>(-std::log(1 - base::RandDouble()) * mean),
                   sanity_bound);
 }
 
diff --git a/net/test/embedded_test_server/embedded_test_server_unittest.cc b/net/test/embedded_test_server/embedded_test_server_unittest.cc
index dc13494..09fc1b6 100644
--- a/net/test/embedded_test_server/embedded_test_server_unittest.cc
+++ b/net/test/embedded_test_server/embedded_test_server_unittest.cc
@@ -17,9 +17,12 @@
 #include "base/synchronization/lock.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
+#include "net/base/elements_upload_data_stream.h"
 #include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
 #include "net/http/http_response_headers.h"
 #include "net/log/net_log_source.h"
 #include "net/socket/client_socket_factory.h"
@@ -621,6 +624,35 @@
   }
 }
 
+TEST_P(EmbeddedTestServerTest, LargePost) {
+  // HTTP/2's default flow-control window is 65K. Send a larger request body
+  // than that to verify the server correctly updates flow control.
+  std::string large_post_body(100 * 1024, 'a');
+  server_->RegisterRequestMonitor(
+      base::BindLambdaForTesting([=](const HttpRequest& request) {
+        EXPECT_EQ(request.method, METHOD_POST);
+        EXPECT_TRUE(request.has_content);
+        EXPECT_EQ(large_post_body, request.content);
+      }));
+
+  server_->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  ASSERT_TRUE(server_->Start());
+
+  auto reader = std::make_unique<UploadBytesElementReader>(
+      large_post_body.data(), large_post_body.size());
+  auto stream = ElementsUploadDataStream::CreateWithReader(std::move(reader),
+                                                           /*identifier=*/0);
+
+  TestDelegate delegate;
+  std::unique_ptr<URLRequest> request(
+      context_->CreateRequest(server_->GetURL("/test"), DEFAULT_PRIORITY,
+                              &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
+  request->set_method("POST");
+  request->set_upload(std::move(stream));
+  request->Start();
+  delegate.RunUntilComplete();
+}
+
 INSTANTIATE_TEST_SUITE_P(EmbeddedTestServerTestInstantiation,
                          EmbeddedTestServerTest,
                          testing::ValuesIn(EmbeddedTestServerConfigs()));
diff --git a/net/test/embedded_test_server/http2_connection.cc b/net/test/embedded_test_server/http2_connection.cc
index 2fca983..ee82fdc 100644
--- a/net/test/embedded_test_server/http2_connection.cc
+++ b/net/test/embedded_test_server/http2_connection.cc
@@ -411,11 +411,21 @@
 
 bool Http2Connection::OnDataForStream(StreamId stream_id,
                                       absl::string_view data) {
+  auto request = request_map_.find(stream_id);
+  if (request == request_map_.end()) {
+    // We should not receive data before receiving headers.
+    return false;
+  }
+
+  request->second->has_content = true;
+  request->second->content.append(data.data(), data.size());
+  adapter_->MarkDataConsumedForStream(stream_id, data.size());
   return true;
 }
 
 bool Http2Connection::OnDataPaddingLength(StreamId stream_id,
                                           size_t padding_length) {
+  adapter_->MarkDataConsumedForStream(stream_id, padding_length);
   return true;
 }
 
diff --git a/sandbox/policy/mac/sandbox_mac.mm b/sandbox/policy/mac/sandbox_mac.mm
index bf0490a..228f84ae 100644
--- a/sandbox/policy/mac/sandbox_mac.mm
+++ b/sandbox/policy/mac/sandbox_mac.mm
@@ -12,6 +12,7 @@
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 #include "sandbox/policy/mac/audio.sb.h"
@@ -25,7 +26,9 @@
 #include "sandbox/policy/mac/print_backend.sb.h"
 #include "sandbox/policy/mac/print_compositor.sb.h"
 #include "sandbox/policy/mac/renderer.sb.h"
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 #include "sandbox/policy/mac/screen_ai.sb.h"
+#endif
 #include "sandbox/policy/mac/speech_recognition.sb.h"
 #include "sandbox/policy/mac/utility.sb.h"
 #include "sandbox/policy/mojom/sandbox.mojom.h"
@@ -84,9 +87,11 @@
     case sandbox::mojom::Sandbox::kPrintCompositor:
       profile += kSeatbeltPolicyString_print_compositor;
       break;
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     case sandbox::mojom::Sandbox::kScreenAI:
       profile += kSeatbeltPolicyString_screen_ai;
       break;
+#endif
     case sandbox::mojom::Sandbox::kSpeechRecognition:
       profile += kSeatbeltPolicyString_speech_recognition;
       break;
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index 88ba3bd..f20c7697 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -42,6 +42,7 @@
 #include "base/win/sid.h"
 #include "base/win/win_util.h"
 #include "base/win/windows_version.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 #include "sandbox/features.h"
@@ -714,7 +715,9 @@
   // Post-startup mitigations.
   mitigations = MITIGATION_DLL_SEARCH_ORDER;
   if (!cmd_line.HasSwitch(switches::kAllowThirdPartyModules) &&
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
       sandbox_type != Sandbox::kScreenAI &&
+#endif
       sandbox_type != Sandbox::kSpeechRecognition &&
       sandbox_type != Sandbox::kMediaFoundationCdm) {
     mitigations |= MITIGATION_FORCE_MS_SIGNED_BINS;
@@ -1212,8 +1215,10 @@
 #endif
     case Sandbox::kAudio:
       return "Audio";
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     case Sandbox::kScreenAI:
       return "Screen AI";
+#endif
     case Sandbox::kSpeechRecognition:
       return "Speech Recognition";
     case Sandbox::kPdfConversion:
diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc
index 5e7f098..ecc0a635 100644
--- a/sandbox/win/src/broker_services.cc
+++ b/sandbox/win/src/broker_services.cc
@@ -604,13 +604,6 @@
     JobTracker* tracker =
         new JobTracker(std::move(policy_base), process_info.process_id());
 
-    // Verify that the process is actually in the specified job. This should
-    // only fail if something has gone wrong during process creation.
-    BOOL in_job;
-    CHECK(
-        job_handle &&
-        ::IsProcessInJob(process_info.process_handle(), job_handle, &in_job) &&
-        in_job);
     // Post the tracker to the tracking thread, then associate the job with
     // the tracker. The worker thread takes ownership of these objects.
     CHECK(::PostQueuedCompletionStatus(
diff --git a/sandbox/win/src/sandbox_policy_base.h b/sandbox/win/src/sandbox_policy_base.h
index c80221d..b898491 100644
--- a/sandbox/win/src/sandbox_policy_base.h
+++ b/sandbox/win/src/sandbox_policy_base.h
@@ -175,8 +175,8 @@
   // SetJobLevel().
   ResultCode InitJob();
 
-  // Returns the handle for this policy's job, or INVALID_HANDLE_VALUE if the
-  // job is not initialized.
+  // Returns the handle for this policy's job, or nullptr if the job is
+  // not initialized.
   HANDLE GetJobHandle();
 
   // Returns true if a job is associated with this policy.
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index d5fd2bc..2856522 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -402,11 +402,6 @@
     }
   }
 
-  network::URLLoader::LogConcerningRequestHeaders(
-      modified_headers, /*added_during_redirect=*/true);
-  network::URLLoader::LogConcerningRequestHeaders(
-      modified_cors_exempt_headers, /*added_during_redirect=*/true);
-
   for (const auto& name : removed_headers) {
     request_.headers.RemoveHeader(name);
     request_.cors_exempt_headers.RemoveHeader(name);
diff --git a/services/network/cors/cors_url_loader_factory.cc b/services/network/cors/cors_url_loader_factory.cc
index 8a3680ce..48af778 100644
--- a/services/network/cors/cors_url_loader_factory.cc
+++ b/services/network/cors/cors_url_loader_factory.cc
@@ -534,9 +534,6 @@
     return false;
   }
 
-  URLLoader::LogConcerningRequestHeaders(request.headers,
-                                         false /* added_during_redirect */);
-
   // Specifying CredentialsMode::kSameOrigin without an initiator origin doesn't
   // make sense.
   if (request.credentials_mode == mojom::CredentialsMode::kSameOrigin &&
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 9e566d1..2ae010d 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/callback_helpers.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/system/functions.h"
 #include "net/base/load_flags.h"
@@ -1714,75 +1713,6 @@
   EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code);
 }
 
-TEST_F(CorsURLLoaderTest, NoConcerningRequestHeadersLoggedCorrectly) {
-  base::HistogramTester histograms;
-
-  ResourceRequest request;
-  request.mode = mojom::RequestMode::kNoCors;
-  request.credentials_mode = mojom::CredentialsMode::kInclude;
-  request.url = GURL("https://example.com/");
-  request.request_initiator = url::Origin::Create(GURL("https://example.com"));
-  request.headers.SetHeader("Not", "Concerning");
-  request.headers.SetHeader("Totally", "Fine");
-
-  CreateLoaderAndStart(request);
-  RunUntilCreateLoaderAndStartCalled();
-  NotifyLoaderClientOnReceiveResponse();
-  NotifyLoaderClientOnComplete(net::OK);
-  RunUntilComplete();
-
-  EXPECT_TRUE(IsNetworkLoaderStarted());
-  EXPECT_FALSE(client().has_received_redirect());
-  EXPECT_TRUE(client().has_received_response());
-  EXPECT_TRUE(client().has_received_completion());
-  EXPECT_EQ(net::OK, client().completion_status().error_code);
-
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.PresentOnStart", true, 0);
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.PresentOnStart", false, 1);
-}
-
-TEST_F(CorsURLLoaderTest, ConcerningRequestHeadersLoggedCorrectly) {
-  using ConcerningHeaderId = URLLoader::ConcerningHeaderId;
-  base::HistogramTester histograms;
-
-  ResourceRequest request;
-  request.mode = mojom::RequestMode::kNoCors;
-  request.credentials_mode = mojom::CredentialsMode::kInclude;
-  request.url = GURL("https://example.com/");
-  request.request_initiator = url::Origin::Create(GURL("https://example.com"));
-  request.headers.SetHeader(net::HttpRequestHeaders::kConnection, "Close");
-  request.headers.SetHeader(net::HttpRequestHeaders::kCookie, "BadIdea=true");
-
-  CreateLoaderAndStart(request);
-  RunUntilCreateLoaderAndStartCalled();
-  NotifyLoaderClientOnReceiveResponse();
-  NotifyLoaderClientOnComplete(net::OK);
-  RunUntilComplete();
-
-  EXPECT_TRUE(IsNetworkLoaderStarted());
-  EXPECT_FALSE(client().has_received_redirect());
-  EXPECT_TRUE(client().has_received_response());
-  EXPECT_TRUE(client().has_received_completion());
-  EXPECT_EQ(net::OK, client().completion_status().error_code);
-
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.PresentOnStart", true, 1);
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.PresentOnStart", false, 0);
-  for (int i = 0; i < static_cast<int>(ConcerningHeaderId::kMaxValue); ++i) {
-    if (i == static_cast<int>(ConcerningHeaderId::kConnection) ||
-        i == static_cast<int>(ConcerningHeaderId::kCookie)) {
-      histograms.ExpectBucketCount(
-          "NetworkService.ConcerningRequestHeader.HeaderPresentOnStart", i, 1);
-    } else {
-      histograms.ExpectBucketCount(
-          "NetworkService.ConcerningRequestHeader.HeaderPresentOnStart", i, 0);
-    }
-  }
-}
-
 TEST_F(CorsURLLoaderTest, SetHostHeaderOnRedirectFails) {
   CreateLoaderAndStart(GURL("https://example.com/"),
                        GURL("https://example.com/path"),
diff --git a/services/network/test/test_url_loader_factory.cc b/services/network/test/test_url_loader_factory.cc
index cb551f7..94fe2e1 100644
--- a/services/network/test/test_url_loader_factory.cc
+++ b/services/network/test/test_url_loader_factory.cc
@@ -17,6 +17,38 @@
 
 namespace network {
 
+TestURLLoaderFactory::TestURLLoader::FollowRedirectParams::
+    FollowRedirectParams() = default;
+TestURLLoaderFactory::TestURLLoader::FollowRedirectParams::
+    ~FollowRedirectParams() = default;
+
+TestURLLoaderFactory::TestURLLoader::FollowRedirectParams::FollowRedirectParams(
+    FollowRedirectParams&& other) = default;
+
+TestURLLoaderFactory::TestURLLoader::FollowRedirectParams&
+TestURLLoaderFactory::TestURLLoader::FollowRedirectParams::operator=(
+    FollowRedirectParams&& other) = default;
+
+TestURLLoaderFactory::TestURLLoader::TestURLLoader(
+    mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver)
+    : receiver_(this, std::move(url_loader_receiver)) {}
+
+TestURLLoaderFactory::TestURLLoader::~TestURLLoader() = default;
+
+void TestURLLoaderFactory::TestURLLoader::FollowRedirect(
+    const std::vector<std::string>& removed_headers,
+    const net::HttpRequestHeaders& modified_headers,
+    const net::HttpRequestHeaders& modified_cors_exempt_headers,
+    const absl::optional<GURL>& new_url) {
+  FollowRedirectParams params;
+  params.removed_headers = removed_headers;
+  params.modified_headers = modified_headers;
+  params.modified_cors_exempt_headers = modified_cors_exempt_headers;
+  params.new_url = new_url;
+
+  follow_redirect_params_.emplace_back(std::move(params));
+}
+
 TestURLLoaderFactory::PendingRequest::PendingRequest() = default;
 TestURLLoaderFactory::PendingRequest::~PendingRequest() = default;
 
@@ -31,10 +63,11 @@
 TestURLLoaderFactory::Response& TestURLLoaderFactory::Response::operator=(
     Response&&) = default;
 
-TestURLLoaderFactory::TestURLLoaderFactory()
+TestURLLoaderFactory::TestURLLoaderFactory(bool observe_loader_requests)
     : weak_wrapper_(
           base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-              this)) {}
+              this)),
+      observe_loader_requests_(observe_loader_requests) {}
 
 TestURLLoaderFactory::~TestURLLoaderFactory() {
   weak_wrapper_->Detach();
@@ -132,6 +165,12 @@
     return;
 
   PendingRequest pending_request;
+
+  if (observe_loader_requests_) {
+    pending_request.test_url_loader =
+        std::make_unique<TestURLLoader>(std::move(receiver));
+  }
+
   pending_request.client = std::move(client_remote);
   pending_request.request_id = request_id;
   pending_request.options = options;
diff --git a/services/network/test/test_url_loader_factory.h b/services/network/test/test_url_loader_factory.h
index 18a4cc1..fee4e73 100644
--- a/services/network/test/test_url_loader_factory.h
+++ b/services/network/test/test_url_loader_factory.h
@@ -28,12 +28,55 @@
 // would prime it with response data for arbitrary URLs.
 class TestURLLoaderFactory : public mojom::URLLoaderFactory {
  public:
+  // A helper class to bind a URLLoader observe method invocations on it.
+  class TestURLLoader final : public network::mojom::URLLoader {
+   public:
+    struct FollowRedirectParams {
+      FollowRedirectParams();
+      ~FollowRedirectParams();
+      FollowRedirectParams(FollowRedirectParams&& other);
+      FollowRedirectParams& operator=(FollowRedirectParams&& other);
+
+      std::vector<std::string> removed_headers;
+      net::HttpRequestHeaders modified_headers;
+      net::HttpRequestHeaders modified_cors_exempt_headers;
+      absl::optional<GURL> new_url;
+    };
+
+    explicit TestURLLoader(
+        mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver);
+    ~TestURLLoader() override;
+
+    TestURLLoader(const TestURLLoader&) = delete;
+    TestURLLoader& operator=(const TestURLLoader&) = delete;
+
+    // network::mojom::URLLoader overrides.
+    void FollowRedirect(
+        const std::vector<std::string>& removed_headers,
+        const net::HttpRequestHeaders& modified_headers,
+        const net::HttpRequestHeaders& modified_cors_exempt_headers,
+        const absl::optional<GURL>& new_url) override;
+    void SetPriority(net::RequestPriority priority,
+                     int32_t intra_priority_value) override {}
+    void PauseReadingBodyFromNet() override {}
+    void ResumeReadingBodyFromNet() override {}
+
+    const std::vector<FollowRedirectParams>& follow_redirect_params() const {
+      return follow_redirect_params_;
+    }
+
+   private:
+    std::vector<FollowRedirectParams> follow_redirect_params_;
+    mojo::Receiver<network::mojom::URLLoader> receiver_;
+  };
+
   struct PendingRequest {
     PendingRequest();
     ~PendingRequest();
     PendingRequest(PendingRequest&& other);
     PendingRequest& operator=(PendingRequest&& other);
 
+    std::unique_ptr<TestURLLoader> test_url_loader;
     mojo::Remote<mojom::URLLoaderClient> client;
     int32_t request_id;
     uint32_t options;
@@ -58,7 +101,7 @@
     kSendHeadersOnNetworkError = 0x2,
   };
 
-  TestURLLoaderFactory();
+  explicit TestURLLoaderFactory(bool observe_loader_requests = false);
 
   TestURLLoaderFactory(const TestURLLoaderFactory&) = delete;
   TestURLLoaderFactory& operator=(const TestURLLoaderFactory&) = delete;
@@ -207,6 +250,10 @@
   Interceptor interceptor_;
   mojo::ReceiverSet<network::mojom::URLLoaderFactory> receivers_;
   size_t total_requests_ = 0;
+
+  // Whether the pending URLLoader in `CreateLoaderAndStart()` should be bound
+  // to observe the method invocations to it (e.g. FollowRedirect).
+  const bool observe_loader_requests_;
 };
 
 }  // namespace network
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 84f72973..0237297 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -106,8 +106,6 @@
 
 namespace {
 
-using ConcerningHeaderId = URLLoader::ConcerningHeaderId;
-
 // Cannot use 0, because this means "default" in
 // mojo::core::Core::CreateDataPipe
 constexpr size_t kBlockedBodyAllocationSize = 1;
@@ -287,22 +285,6 @@
              net::CookieInclusionStatus::EXCLUDE_DOMAIN_NON_ASCII);
 }
 
-// Concerning headers that consumers probably shouldn't be allowed to set.
-// Gathering numbers on these before adding them to kUnsafeHeaders.
-const struct {
-  const char* name;
-  ConcerningHeaderId histogram_id;
-} kConcerningHeaders[] = {
-    {net::HttpRequestHeaders::kConnection, ConcerningHeaderId::kConnection},
-    {net::HttpRequestHeaders::kCookie, ConcerningHeaderId::kCookie},
-    {"Date", ConcerningHeaderId::kDate},
-    {"Expect", ConcerningHeaderId::kExpect},
-    // The referer is passed in from the caller on a per-request basis, but
-    // there's a separate field for it that should be used instead.
-    {net::HttpRequestHeaders::kReferer, ConcerningHeaderId::kReferer},
-    {"Via", ConcerningHeaderId::kVia},
-};
-
 // Parses AcceptCHFrame and removes client hints already in the headers.
 std::vector<mojom::WebClientHintsType> ComputeAcceptCHFrameHints(
     const std::string& accept_ch_frame,
@@ -982,7 +964,6 @@
 URLLoader::~URLLoader() {
   TRACE_EVENT("loading", "URLLoader::~URLLoader",
               perfetto::TerminatingFlow::FromPointer(this));
-  RecordBodyReadFromNetBeforePausedIfNeeded();
   if (keepalive_ && keepalive_statistics_recorder_) {
     keepalive_statistics_recorder_->OnLoadFinished(
         *factory_params_->top_frame_id, keepalive_request_size_);
@@ -1021,10 +1002,6 @@
     return;
   }
 
-  if (!modified_headers.IsEmpty())
-    LogConcerningRequestHeaders(modified_headers,
-                                true /* added_during_redirect */);
-
   deferred_redirect_url_.reset();
   new_redirect_url_ = new_url;
 
@@ -1053,17 +1030,7 @@
   // request indicates that the response was cached, there could still be
   // network activity involved. For example, the response was only partially
   // cached.
-  //
-  // On the other hand, we only report BodyReadFromNetBeforePaused histogram
-  // when we are sure that the response body hasn't been read from cache. This
-  // avoids polluting the histogram data with data points from cached responses.
   should_pause_reading_body_ = true;
-
-  if (read_in_progress_) {
-    update_body_read_before_paused_ = true;
-  } else {
-    body_read_before_paused_ = url_request_->GetRawBodyBytes();
-  }
 }
 
 void URLLoader::ResumeReadingBodyFromNet() {
@@ -1687,10 +1654,6 @@
       reported_total_encoded_bytes_ = total_encoded_bytes;
     }
   }
-  if (update_body_read_before_paused_) {
-    update_body_read_before_paused_ = false;
-    body_read_before_paused_ = url_request_->GetRawBodyBytes();
-  }
 
   bool complete_read = true;
   if (consumer_handle_.is_valid()) {
@@ -1875,42 +1838,6 @@
   return pointer->get();
 }
 
-// static
-void URLLoader::LogConcerningRequestHeaders(
-    const net::HttpRequestHeaders& request_headers,
-    bool added_during_redirect) {
-  net::HttpRequestHeaders::Iterator it(request_headers);
-
-  bool concerning_header_found = false;
-
-  while (it.GetNext()) {
-    for (const auto& header : kConcerningHeaders) {
-      if (base::EqualsCaseInsensitiveASCII(header.name, it.name())) {
-        concerning_header_found = true;
-        if (added_during_redirect) {
-          UMA_HISTOGRAM_ENUMERATION(
-              "NetworkService.ConcerningRequestHeader.HeaderAddedOnRedirect",
-              header.histogram_id);
-        } else {
-          UMA_HISTOGRAM_ENUMERATION(
-              "NetworkService.ConcerningRequestHeader.HeaderPresentOnStart",
-              header.histogram_id);
-        }
-      }
-    }
-  }
-
-  if (added_during_redirect) {
-    UMA_HISTOGRAM_BOOLEAN(
-        "NetworkService.ConcerningRequestHeader.AddedOnRedirect",
-        concerning_header_found);
-  } else {
-    UMA_HISTOGRAM_BOOLEAN(
-        "NetworkService.ConcerningRequestHeader.PresentOnStart",
-        concerning_header_found);
-  }
-}
-
 void URLLoader::OnAuthCredentials(
     const absl::optional<net::AuthCredentials>& credentials) {
   auth_challenge_responder_receiver_.reset();
@@ -2244,22 +2171,6 @@
   return pending_write_ || response_body_stream_.is_valid();
 }
 
-void URLLoader::RecordBodyReadFromNetBeforePausedIfNeeded() {
-  if (update_body_read_before_paused_)
-    body_read_before_paused_ = url_request_->GetRawBodyBytes();
-  if (body_read_before_paused_ != -1) {
-    if (!url_request_->was_cached()) {
-      UMA_HISTOGRAM_COUNTS_1M("Network.URLLoader.BodyReadFromNetBeforePaused",
-                              body_read_before_paused_);
-    } else {
-      DVLOG(1) << "The request has been paused, but "
-               << "Network.URLLoader.BodyReadFromNetBeforePaused is not "
-               << "reported because the response body may be from cache. "
-               << "body_read_before_paused_: " << body_read_before_paused_;
-    }
-  }
-}
-
 void URLLoader::ResumeStart() {
   url_request_->LogUnblocked();
   url_request_->Start();
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index ca904eb..766c643 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -96,25 +96,6 @@
       public mojom::AuthChallengeResponder,
       public mojom::ClientCertificateResponder {
  public:
-  // Enumeration for UMA histograms logged by LogConcerningRequestHeaders().
-  // Entries should not be renumbered and numeric values should never be reused.
-  // Please keep in sync with "NetworkServiceConcerningRequestHeaders" in
-  // src/tools/metrics/histograms/enums.xml.
-  enum class ConcerningHeaderId {
-    kConnection = 0,
-    kCookie = 1,
-    kCookie2 = 2,
-    kContentTransferEncoding = 3,
-    kDate = 4,
-    kExpect = 5,
-    kKeepAlive = 6,
-    kReferer = 7,
-    kTe = 8,
-    kTransferEncoding = 9,
-    kVia = 10,
-    kMaxValue = kVia,
-  };
-
   using DeleteCallback = base::OnceCallback<void(URLLoader* loader)>;
 
   // Holds a sync and async implementation of URLLoaderClient. The sync
@@ -288,10 +269,6 @@
 
   static const void* const kUserDataKey;
 
-  static void LogConcerningRequestHeaders(
-      const net::HttpRequestHeaders& request_headers,
-      bool added_during_redirect);
-
   static bool HasFetchStreamingUploadBody(const ResourceRequest*);
 
   static absl::optional<net::IsolationInfo> GetIsolationInfo(
@@ -551,16 +528,6 @@
   // The response body stream is open, but transferring data is paused.
   bool paused_reading_body_ = false;
 
-  // Whether to update |body_read_before_paused_| after the pending read is
-  // completed (or when the response body stream is closed).
-  bool update_body_read_before_paused_ = false;
-  // The number of bytes obtained by the reads initiated before the last
-  // PauseReadingBodyFromNet() call. -1 means the request hasn't been paused.
-  // The body may be read from cache or network. So even if this value is not
-  // -1, we still need to check whether it is from network before reporting it
-  // as BodyReadFromNetBeforePaused.
-  int64_t body_read_before_paused_ = -1;
-
   // This is used to compute the delta since last time received
   // encoded body size was reported to the client.
   int64_t reported_total_encoded_bytes_ = 0;
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index fbaf103..8b1bb24 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -20,7 +20,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "base/metrics/statistics_recorder.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
@@ -160,9 +159,6 @@
   return base::BindOnce([](URLLoader* /* loader*/) { NOTREACHED(); });
 }
 
-constexpr char kBodyReadFromNetBeforePausedHistogram[] =
-    "Network.URLLoader.BodyReadFromNetBeforePaused";
-
 constexpr char kTestAuthURL[] = "/auth-basic?password=PASS&realm=REALM";
 
 constexpr char kInsecureHost[] = "othersite.test";
@@ -602,25 +598,6 @@
                          resource_address_space);
 }
 
-// Returns whether monitoring was successfully set up.
-// |*output_sample| needs to stay valid until monitoring is stopped.
-std::unique_ptr<base::StatisticsRecorder::ScopedHistogramSampleObserver>
-StartMonitorBodyReadFromNetBeforePausedHistogram(
-    const base::RepeatingClosure& quit_closure,
-    base::HistogramBase::Sample* output_sample) {
-  return std::make_unique<
-      base::StatisticsRecorder::ScopedHistogramSampleObserver>(
-      kBodyReadFromNetBeforePausedHistogram,
-      base::BindRepeating(
-          [](const base::RepeatingClosure& quit_closure,
-             base::HistogramBase::Sample* output, const char* histogram_name,
-             uint64_t name_hash, base::HistogramBase::Sample sample) {
-            *output = sample;
-            quit_closure.Run();
-          },
-          quit_closure, output_sample));
-}
-
 std::string CookieOrLineToString(const mojom::CookieOrLinePtr& cookie_or_line) {
   switch (cookie_or_line->which()) {
     case mojom::CookieOrLine::Tag::kCookie:
@@ -2607,11 +2584,6 @@
   const char* const kPath = "/hello.html";
   const char* const kBodyContents = "This is the data as you requested.";
 
-  base::HistogramBase::Sample output_sample = -1;
-  base::RunLoop histogram_run_loop;
-  auto histogram_callback = StartMonitorBodyReadFromNetBeforePausedHistogram(
-      histogram_run_loop.QuitClosure(), &output_sample);
-
   net::EmbeddedTestServer server;
   net::test_server::ControllableHttpResponse response_controller(&server,
                                                                  kPath);
@@ -2667,8 +2639,6 @@
 
   delete_run_loop.Run();
   client()->Unbind();
-  histogram_run_loop.Run();
-  EXPECT_EQ(0, output_sample);
 }
 
 TEST_F(URLLoaderTest, PauseReadingBodyFromNetWhenReadIsPending) {
@@ -2676,11 +2646,6 @@
   const char* const kBodyContentsFirstHalf = "This is the first half.";
   const char* const kBodyContentsSecondHalf = "This is the second half.";
 
-  base::HistogramBase::Sample output_sample = -1;
-  base::RunLoop histogram_run_loop;
-  auto histogram_callback = StartMonitorBodyReadFromNetBeforePausedHistogram(
-      histogram_run_loop.QuitClosure(), &output_sample);
-
   net::EmbeddedTestServer server;
   net::test_server::ControllableHttpResponse response_controller(&server,
                                                                  kPath);
@@ -2727,19 +2692,12 @@
 
   delete_run_loop.Run();
   client()->Unbind();
-  histogram_run_loop.Run();
-  EXPECT_LE(0, output_sample);
 }
 
 TEST_F(URLLoaderTest, ResumeReadingBodyFromNetAfterClosingConsumer) {
   const char* const kPath = "/hello.html";
   const char* const kBodyContentsFirstHalf = "This is the first half.";
 
-  base::HistogramBase::Sample output_sample = -1;
-  base::RunLoop histogram_run_loop;
-  auto histogram_callback = StartMonitorBodyReadFromNetBeforePausedHistogram(
-      histogram_run_loop.QuitClosure(), &output_sample);
-
   net::EmbeddedTestServer server;
   net::test_server::ControllableHttpResponse response_controller(&server,
                                                                  kPath);
@@ -2780,8 +2738,6 @@
   loader.reset();
   client()->Unbind();
   delete_run_loop.Run();
-  histogram_run_loop.Run();
-  EXPECT_EQ(0, output_sample);
 }
 
 TEST_F(URLLoaderTest, MultiplePauseResumeReadingBodyFromNet) {
@@ -2789,11 +2745,6 @@
   const char* const kBodyContentsFirstHalf = "This is the first half.";
   const char* const kBodyContentsSecondHalf = "This is the second half.";
 
-  base::HistogramBase::Sample output_sample = -1;
-  base::RunLoop histogram_run_loop;
-  auto histogram_callback = StartMonitorBodyReadFromNetBeforePausedHistogram(
-      histogram_run_loop.QuitClosure(), &output_sample);
-
   net::EmbeddedTestServer server;
   net::test_server::ControllableHttpResponse response_controller(&server,
                                                                  kPath);
@@ -2847,8 +2798,6 @@
 
   loader.reset();
   client()->Unbind();
-  histogram_run_loop.Run();
-  EXPECT_LE(0, output_sample);
 }
 
 TEST_F(URLLoaderTest, UploadBytes) {
@@ -3397,54 +3346,6 @@
   }
 }
 
-TEST_F(URLLoaderTest, RedirectLogsModifiedConcerningHeader) {
-  using ConcerningHeaderId = URLLoader::ConcerningHeaderId;
-  base::HistogramTester histograms;
-
-  TestURLLoaderClient client;
-
-  ResourceRequest request = CreateResourceRequest(
-      "GET", test_server()->GetURL("/redirect307-to-echo"));
-
-  base::RunLoop delete_run_loop;
-  mojo::Remote<mojom::URLLoader> loader;
-  std::unique_ptr<URLLoader> url_loader;
-  context().mutable_factory_params().process_id = mojom::kBrowserProcessId;
-  context().mutable_factory_params().is_corb_enabled = false;
-  url_loader = URLLoaderOptions().MakeURLLoader(
-      context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
-      loader.BindNewPipeAndPassReceiver(), request, client.CreateRemote());
-
-  client.RunUntilRedirectReceived();
-
-  net::HttpRequestHeaders redirect_headers;
-  redirect_headers.SetHeader(net::HttpRequestHeaders::kReferer,
-                             "https://somewhere.test/");
-  redirect_headers.SetHeader("Via", "Albuquerque");
-  loader->FollowRedirect({}, redirect_headers, {}, absl::nullopt);
-
-  client.RunUntilComplete();
-  delete_run_loop.Run();
-
-  EXPECT_TRUE(client.has_received_completion());
-  EXPECT_EQ(net::OK, client.completion_status().error_code);
-
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.AddedOnRedirect", true, 1);
-  histograms.ExpectBucketCount(
-      "NetworkService.ConcerningRequestHeader.AddedOnRedirect", false, 0);
-  for (int i = 0; i < static_cast<int>(ConcerningHeaderId::kMaxValue); ++i) {
-    if (i == static_cast<int>(ConcerningHeaderId::kReferer) ||
-        i == static_cast<int>(ConcerningHeaderId::kVia)) {
-      histograms.ExpectBucketCount(
-          "NetworkService.ConcerningRequestHeader.HeaderAddedOnRedirect", i, 1);
-    } else {
-      histograms.ExpectBucketCount(
-          "NetworkService.ConcerningRequestHeader.HeaderAddedOnRedirect", i, 0);
-    }
-  }
-}
-
 // Test the client can remove headers during a redirect.
 TEST_F(URLLoaderTest, RedirectRemoveHeader) {
   ResourceRequest request = CreateResourceRequest(
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.cc
index 6a83b44..9847122 100644
--- a/services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.cc
+++ b/services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.cc
@@ -58,9 +58,10 @@
 #else
   base::TimeDelta mean_time = base::Minutes(30);
 #endif
-  // Compute the actual delay before sampling using a Poisson process.
+  // Compute the actual delay before sampling using a Poisson process. Use
+  // `1-RandDouble()` to avoid log(0).
   double uniform = base::RandDouble();
-  return -std::log(uniform) * mean_time;
+  return -std::log(1 - uniform) * mean_time;
 }
 
 }  // namespace memory_instrumentation
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index a0ac255..cfb9ad2 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -776,11 +776,8 @@
       trace_config, privacy_filtering_enabled);
 
 #if !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
-  uint8_t modes = base::trace_event::TraceLog::RECORDING_MODE;
-  if (!trace_config.event_filters().empty()) {
-    modes |= base::trace_event::TraceLog::FILTERING_MODE;
-  }
-  base::trace_event::TraceLog::GetInstance()->SetEnabled(trace_config, modes);
+  base::trace_event::TraceLog::GetInstance()->SetEnabled(
+      trace_config, base::trace_event::TraceLog::RECORDING_MODE);
 #endif
 }
 
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index b2c8b79..b330273 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -944,8 +944,5 @@
     "skia_resources.pak",
   ]
 
-  deps = [
-    "//skia/public/mojom:mojom_js",
-    "//skia/public/mojom:mojom_webui_js",
-  ]
+  deps = [ "//skia/public/mojom:mojom_js__generator" ]
 }
diff --git a/storage/browser/quota/quota_database_migrations.cc b/storage/browser/quota/quota_database_migrations.cc
index c7930c58..4a40b762 100644
--- a/storage/browser/quota/quota_database_migrations.cc
+++ b/storage/browser/quota/quota_database_migrations.cc
@@ -6,7 +6,10 @@
 
 #include <string>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/sequence_checker.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "components/services/storage/public/cpp/buckets/bucket_id.h"
 #include "components/services/storage/public/cpp/buckets/constants.h"
 #include "sql/database.h"
@@ -78,28 +81,46 @@
     return quota_database.ResetStorage();
 
   if (quota_database.meta_table_->GetVersionNumber() == 5) {
-    if (!MigrateFromVersion5ToVersion7(quota_database))
+    bool success = MigrateFromVersion5ToVersion7(quota_database);
+    RecordMigrationHistogram(/*old_version=*/5, /*new_version=*/7, success);
+    if (!success)
       return false;
   }
 
   if (quota_database.meta_table_->GetVersionNumber() == 6) {
-    if (!MigrateFromVersion6ToVersion7(quota_database))
+    bool success = MigrateFromVersion6ToVersion7(quota_database);
+    RecordMigrationHistogram(/*old_version=*/6, /*new_version=*/7, success);
+    if (!success)
       return false;
   }
 
   if (quota_database.meta_table_->GetVersionNumber() == 7) {
-    if (!MigrateFromVersion7ToVersion8(quota_database))
+    bool success = MigrateFromVersion7ToVersion8(quota_database);
+    RecordMigrationHistogram(/*old_version=*/7, /*new_version=*/8, success);
+    if (!success)
       return false;
   }
 
   if (quota_database.meta_table_->GetVersionNumber() == 8) {
-    if (!MigrateFromVersion8ToVersion9(quota_database))
+    bool success = MigrateFromVersion8ToVersion9(quota_database);
+    RecordMigrationHistogram(/*old_version=*/8, /*new_version=*/9, success);
+    if (!success)
       return false;
   }
 
   return quota_database.meta_table_->GetVersionNumber() == 9;
 }
 
+void QuotaDatabaseMigrations::RecordMigrationHistogram(int old_version,
+                                                       int new_version,
+                                                       bool success) {
+  base::UmaHistogramBoolean(
+      base::StrCat({"Quota.DatabaseMigrationFromV",
+                    base::NumberToString(old_version), "ToV",
+                    base::NumberToString(new_version)}),
+      success);
+}
+
 bool QuotaDatabaseMigrations::MigrateFromVersion5ToVersion7(
     QuotaDatabase& quota_database) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
diff --git a/storage/browser/quota/quota_database_migrations.h b/storage/browser/quota/quota_database_migrations.h
index 633f195..d09cc24 100644
--- a/storage/browser/quota/quota_database_migrations.h
+++ b/storage/browser/quota/quota_database_migrations.h
@@ -23,6 +23,10 @@
   static bool UpgradeSchema(QuotaDatabase& quota_database);
 
  private:
+  static void RecordMigrationHistogram(int old_version,
+                                       int new_version,
+                                       bool success);
+
   static bool MigrateFromVersion5ToVersion7(QuotaDatabase& quota_database);
   static bool MigrateFromVersion6ToVersion7(QuotaDatabase& quota_database);
   static bool MigrateFromVersion7ToVersion8(QuotaDatabase& quota_database);
diff --git a/storage/browser/quota/quota_database_migrations_unittest.cc b/storage/browser/quota/quota_database_migrations_unittest.cc
index 2268b2d..0e40be1 100644
--- a/storage/browser/quota/quota_database_migrations_unittest.cc
+++ b/storage/browser/quota/quota_database_migrations_unittest.cc
@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/path_service.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "components/services/storage/public/cpp/constants.h"
 #include "sql/database.h"
 #include "sql/meta_table.h"
@@ -30,7 +33,10 @@
 
 class QuotaDatabaseMigrationsTest : public testing::Test {
  public:
-  void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); }
+  void SetUp() override {
+    ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
+    histograms_ = std::make_unique<base::HistogramTester>();
+  }
 
   base::FilePath ProfilePath() { return temp_directory_.GetPath(); }
 
@@ -93,7 +99,13 @@
     return db.db_->GetSchema();
   }
 
+  size_t GetTotalHistogramCount() {
+    return histograms_->GetTotalCountsForPrefix("Quota.DatabaseMigration")
+        .size();
+  }
+
   base::ScopedTempDir temp_directory_;
+  std::unique_ptr<base::HistogramTester> histograms_;
 };
 
 // Verify that the schema created by a new `QuotaDatabase` instance matches the
@@ -172,6 +184,14 @@
                   &db, "SELECT host FROM quota ORDER BY host ASC", "|", ","));
 
     EXPECT_EQ(GetCurrentSchema(), RemoveQuotes(db.GetSchema()));
+
+    EXPECT_EQ(GetTotalHistogramCount(), 3u);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV5ToV7",
+                                   /*sample=*/true, /*expected_count=*/1);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV7ToV8",
+                                   /*sample=*/true, /*expected_count=*/1);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV8ToV9",
+                                   /*sample=*/true, /*expected_count=*/1);
   }
 }
 
@@ -244,6 +264,14 @@
                   &db, "SELECT host FROM quota ORDER BY host ASC", "|", ","));
 
     EXPECT_EQ(GetCurrentSchema(), RemoveQuotes(db.GetSchema()));
+
+    EXPECT_EQ(GetTotalHistogramCount(), 3u);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV6ToV7",
+                                   /*sample=*/true, /*expected_count=*/1);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV7ToV8",
+                                   /*sample=*/true, /*expected_count=*/1);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV8ToV9",
+                                   /*sample=*/true, /*expected_count=*/1);
   }
 }
 
@@ -315,6 +343,12 @@
                   &db, "SELECT host FROM quota ORDER BY host ASC", "|", ","));
 
     EXPECT_EQ(GetCurrentSchema(), RemoveQuotes(db.GetSchema()));
+
+    EXPECT_EQ(GetTotalHistogramCount(), 2u);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV7ToV8",
+                                   /*sample=*/true, /*expected_count=*/1);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV8ToV9",
+                                   /*sample=*/true, /*expected_count=*/1);
   }
 }
 
@@ -392,6 +426,10 @@
                   &db, "SELECT host FROM quota ORDER BY host ASC", "|", ","));
 
     EXPECT_EQ(GetCurrentSchema(), RemoveQuotes(db.GetSchema()));
+
+    EXPECT_EQ(GetTotalHistogramCount(), 1u);
+    histograms_->ExpectBucketCount("Quota.DatabaseMigrationFromV8ToV9",
+                                   /*sample=*/true, /*expected_count=*/1);
   }
 }
 
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 0a37826e..4d36701 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5923,9 +5923,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5937,8 +5937,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -6090,9 +6090,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6104,8 +6104,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -6242,9 +6242,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6256,8 +6256,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 32454d3..b3209b2 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -3670,1949 +3670,6 @@
       }
     ]
   },
-  "Linux MSan Focal": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "absl_hardening_tests",
-        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "accessibility_unittests",
-        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "angle_unittests",
-        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
-        "use_isolated_scripts_api": true
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "app_shell_unittests",
-        "test_id_prefix": "ninja://extensions/shell:app_shell_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "aura_unittests",
-        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_common_unittests",
-        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_fuzzer_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_fuzzer_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_heap_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_platform_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webkit_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_crypto_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_ssl_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
-        },
-        "test": "browser_tests",
-        "test_id_prefix": "ninja://chrome/test:browser_tests/"
-      },
-      {
-        "args": [
-          "--gtest_filter=-*UsingRealWebcam*",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "capture_unittests",
-        "test_id_prefix": "ninja://media/capture:capture_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_unittests",
-        "test_id_prefix": "ninja://media/cast:cast_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cc_unittests",
-        "test_id_prefix": "ninja://cc:cc_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "chrome_app_unittests",
-        "test_id_prefix": "ninja://chrome/test:chrome_app_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "chromedriver_unittests",
-        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "color_unittests",
-        "test_id_prefix": "ninja://ui/color:color_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "components_browsertests",
-        "test_id_prefix": "ninja://components:components_browsertests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "components_unittests",
-        "test_id_prefix": "ninja://components:components_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "compositor_unittests",
-        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 8
-        },
-        "test": "content_browsertests",
-        "test_id_prefix": "ninja://content/test:content_browsertests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crashpad_tests",
-        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cronet_tests",
-        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cronet_unittests",
-        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crypto_unittests",
-        "test_id_prefix": "ninja://crypto:crypto_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dbus_unittests",
-        "test_id_prefix": "ninja://dbus:dbus_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "device_unittests",
-        "test_id_prefix": "ninja://device:device_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "display_unittests",
-        "test_id_prefix": "ninja://ui/display:display_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "events_unittests",
-        "test_id_prefix": "ninja://ui/events:events_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "extensions_browsertests",
-        "test_id_prefix": "ninja://extensions:extensions_browsertests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "extensions_unittests",
-        "test_id_prefix": "ninja://extensions:extensions_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "filesystem_service_unittests",
-        "test_id_prefix": "ninja://components/services/filesystem:filesystem_service_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gcm_unit_tests",
-        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gfx_unittests",
-        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gin_unittests",
-        "test_id_prefix": "ninja://gin:gin_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "google_apis_unittests",
-        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gpu_unittests",
-        "test_id_prefix": "ninja://gpu:gpu_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gwp_asan_unittests",
-        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "headless_browsertests",
-        "test_id_prefix": "ninja://headless:headless_browsertests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "headless_unittests",
-        "test_id_prefix": "ninja://headless:headless_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
-        },
-        "test": "interactive_ui_tests",
-        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ipc_tests",
-        "test_id_prefix": "ninja://ipc:ipc_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "latency_unittests",
-        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "libjingle_xmpp_unittests",
-        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "liburlpattern_unittests",
-        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "media_unittests",
-        "test_id_prefix": "ninja://media:media_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "message_center_unittests",
-        "test_id_prefix": "ninja://ui/message_center:message_center_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "midi_unittests",
-        "test_id_prefix": "ninja://media/midi:midi_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "mojo_core_unittests",
-        "test_id_prefix": "ninja://mojo/core:mojo_core_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "mojo_unittests",
-        "test_id_prefix": "ninja://mojo:mojo_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "nacl_loader_unittests",
-        "test_id_prefix": "ninja://components/nacl/loader:nacl_loader_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "native_theme_unittests",
-        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "net_unittests",
-        "test_id_prefix": "ninja://net:net_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "openscreen_unittests",
-        "test_id_prefix": "ninja://chrome/browser/media/router:openscreen_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ozone_x11_unittests",
-        "test_id_prefix": "ninja://ui/ozone:ozone_x11_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "pdf_unittests",
-        "test_id_prefix": "ninja://pdf:pdf_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "perfetto_unittests",
-        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ppapi_unittests",
-        "test_id_prefix": "ninja://ppapi:ppapi_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "printing_unittests",
-        "test_id_prefix": "ninja://printing:printing_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "remoting_unittests",
-        "test_id_prefix": "ninja://remoting:remoting_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sandbox_linux_unittests",
-        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "service_manager_unittests",
-        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "shell_dialogs_unittests",
-        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "skia_unittests",
-        "test_id_prefix": "ninja://skia:skia_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "snapshot_unittests",
-        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sql_unittests",
-        "test_id_prefix": "ninja://sql:sql_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "storage_unittests",
-        "test_id_prefix": "ninja://storage:storage_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sync_integration_tests",
-        "test_id_prefix": "ninja://chrome/test:sync_integration_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_base_unittests",
-        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_touch_selection_unittests",
-        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "unit_tests",
-        "test_id_prefix": "ninja://chrome/test:unit_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "url_unittests",
-        "test_id_prefix": "ninja://url:url_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "views_unittests",
-        "test_id_prefix": "ninja://ui/views:views_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "viz_unittests",
-        "test_id_prefix": "ninja://components/viz:viz_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "vr_common_unittests",
-        "test_id_prefix": "ninja://chrome/browser/vr:vr_common_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "vr_pixeltests",
-        "test_id_prefix": "ninja://chrome/browser/vr:vr_pixeltests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "wm_unittests",
-        "test_id_prefix": "ninja://ui/wm:wm_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "wtf_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "xr_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "xr_browser_tests",
-        "test_id_prefix": "ninja://chrome/test:xr_browser_tests/"
-      },
-      {
-        "args": [
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-20.04"
-            }
-          ],
-          "expiration": 10800,
-          "hard_timeout": 7200,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "zlib_unittests",
-        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
-      }
-    ]
-  },
   "Linux Viz": {
     "additional_compile_targets": [
       "all"
@@ -87397,9 +85454,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -87411,8 +85468,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -87534,9 +85591,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -87548,8 +85605,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -87661,9 +85718,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -87675,8 +85732,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -89017,9 +87074,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89030,8 +87087,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -89184,9 +87241,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89197,8 +87254,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -89336,9 +87393,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89349,8 +87406,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -90872,9 +88929,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -90885,8 +88942,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -91039,9 +89096,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -91052,8 +89109,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -91191,9 +89248,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -91204,8 +89261,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -91871,37 +89928,6 @@
       }
     ]
   },
-  "linux-lacros-tester-rel-reviver": {
-    "additional_compile_targets": [
-      "chrome",
-      "chrome_sandbox",
-      "symupload"
-    ],
-    "gtest_tests": [
-      {
-        "args": [
-          "--gtest_also_run_disabled_tests"
-        ],
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
-        },
-        "test": "browser_tests",
-        "test_id_prefix": "ninja://chrome/test:browser_tests/"
-      }
-    ]
-  },
   "linux-lacros-version-skew-fyi": {
     "additional_compile_targets": [
       "chrome"
@@ -92004,9 +90030,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -92017,8 +90043,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -100188,44 +98214,6 @@
       },
       {
         "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_wpt_tests",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-13"
-            }
-          ],
-          "expiration": 21600,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
-        },
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      },
-      {
-        "args": [
           "--test-type=integration"
         ],
         "check_flakiness_for_new_tests": false,
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index b0673d0..eec7875 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -7123,7 +7123,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7144,7 +7144,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7165,7 +7165,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7187,7 +7187,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7208,7 +7208,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7229,7 +7229,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7250,7 +7250,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7271,7 +7271,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7292,7 +7292,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7313,7 +7313,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7335,7 +7335,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7356,7 +7356,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7377,7 +7377,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7398,7 +7398,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -7421,7 +7421,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7442,7 +7442,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7463,7 +7463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7484,7 +7484,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7505,7 +7505,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7526,7 +7526,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7547,7 +7547,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7568,7 +7568,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7589,7 +7589,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7610,7 +7610,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -7632,7 +7632,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7653,7 +7653,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7674,7 +7674,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7695,7 +7695,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7716,7 +7716,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7737,7 +7737,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7758,7 +7758,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7779,7 +7779,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7800,7 +7800,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7821,7 +7821,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7842,7 +7842,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7863,7 +7863,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7884,7 +7884,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7905,7 +7905,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7926,7 +7926,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7947,7 +7947,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7968,7 +7968,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -7989,7 +7989,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8010,7 +8010,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8031,7 +8031,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8052,7 +8052,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -8095,7 +8095,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8116,7 +8116,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8137,7 +8137,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8158,7 +8158,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8179,7 +8179,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8200,7 +8200,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8221,7 +8221,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8242,7 +8242,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8263,7 +8263,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8284,7 +8284,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8305,7 +8305,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8326,7 +8326,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8347,7 +8347,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8368,7 +8368,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8389,7 +8389,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8410,7 +8410,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8431,7 +8431,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8452,7 +8452,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8473,7 +8473,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8494,7 +8494,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8515,7 +8515,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8536,7 +8536,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8557,7 +8557,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8578,7 +8578,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8599,7 +8599,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8620,7 +8620,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8641,7 +8641,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8662,7 +8662,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8683,7 +8683,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8704,7 +8704,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8725,7 +8725,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8746,7 +8746,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8767,7 +8767,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8788,7 +8788,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8809,7 +8809,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8830,7 +8830,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8852,7 +8852,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8873,7 +8873,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
+              "os": "Ubuntu-20.04"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -18604,11 +18604,11 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18620,8 +18620,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -18785,11 +18785,11 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18801,8 +18801,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
@@ -18947,11 +18947,11 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots"
         ],
-        "description": "Run with ash-chrome version 110.0.5431.0",
+        "description": "Run with ash-chrome version 110.0.5432.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18963,8 +18963,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5431.0",
-              "revision": "version:110.0.5431.0"
+              "location": "lacros_version_skew_tests_v110.0.5432.0",
+              "revision": "version:110.0.5432.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index e23031b..619c712 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -533,13 +533,6 @@
       },
     },
   },
-  'gtest_run_disabled': {
-    '$mixin_append': {
-      'args': [
-        '--gtest_also_run_disabled_tests',
-      ],
-    },
-  },
   # Use of this mixin signals to the recipe that the test uploads its results
   # to result-sink and doesn't need to be wrapped by result_adapter.
   'has_native_resultdb_integration': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index e7c8c5c..c240bee 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -549,6 +549,7 @@
       'Win10 Tests x64 (dbg)',
       'devtools_frontend_linux_blink_light_rel',
       'devtools_frontend_linux_blink_light_rel_fastbuild',
+      'mac-osxbeta-rel',
     ],
     'modifications': {
       'Linux Tests': {
@@ -2125,7 +2126,6 @@
       # Can't run on MSAN because gl_tests uses the hardware driver,
       # which isn't instrumented.
       'Linux MSan Tests',
-      'Linux MSan Focal',
     ],
     'modifications': {
       # TODO(kbr): figure out a better way to specify blocks of
@@ -2251,7 +2251,6 @@
       # Can't run on MSAN because gl_unittests uses the hardware driver,
       # which isn't instrumented.
       'Linux MSan Tests',
-      'Linux MSan Focal',
     ],
     'modifications': {
       'Win10 FYI x64 Exp Release (Intel HD 630)': {
@@ -3191,7 +3190,6 @@
       'Linux ChromiumOS MSan Tests',  # https://crbug.com/831676
       'Linux ChromiumOS MSan Focal',
       'Linux MSan Tests',  # https://crbug.com/831676
-      'Linux MSan Focal',  # https://crbug.com/831676
     ],
     'replacements': {
       # TODO(crbug.com/1078982): Remove once the test is fixed on 10.15.4.
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 9e14bcc..7a86a62 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4140,14 +4140,6 @@
       },
     },
 
-    'linux_lacros_reviver_tests': {
-      'browser_tests': {
-        'swarming': {
-          'shards': 10,
-        },
-      },
-    },
-
     'linux_lacros_specific_gtests': {
       'lacros_chrome_unittests': {},
       'ozone_unittests': {},
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 8d5c3e9..763fd76c 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5431.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5432.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 110.0.5431.0',
+    'description': 'Run with ash-chrome version 110.0.5432.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v110.0.5431.0',
-          'revision': 'version:110.0.5431.0',
+          'location': 'lacros_version_skew_tests_v110.0.5432.0',
+          'revision': 'version:110.0.5432.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index d9f2f5d1..a6c216de 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2724,22 +2724,6 @@
           '--test-launcher-print-test-stdio=always',
         ],
       },
-      'Linux MSan Focal': {
-        'mixins': [
-          'linux-focal',
-          'x86-64',
-        ],
-        'swarming': {
-          'expiration': 10800,
-          'hard_timeout': 7200,
-        },
-        'test_suites': {
-          'gtest_tests': 'chromium_linux_and_gl_gtests',
-        },
-        'args': [
-          '--test-launcher-print-test-stdio=always',
-        ],
-      },
       'Linux Viz': {
         'mixins': [
           'linux-bionic',
@@ -3421,21 +3405,6 @@
           'gtest_tests': 'linux_lacros_gtests',
         },
       },
-      'linux-lacros-tester-rel-reviver': {
-        'mixins': [
-          'isolate_profile_data',
-          'linux-bionic',
-          'gtest_run_disabled',
-        ],
-        'additional_compile_targets': [
-          'chrome',
-          'chrome_sandbox',
-          'symupload'
-        ],
-        'test_suites': {
-          'gtest_tests': 'linux_lacros_reviver_tests',
-        },
-      },
       'linux-lacros-version-skew-fyi': {
         'additional_compile_targets': [
           'chrome',
@@ -5055,7 +5024,7 @@
       },
       'Linux MSan Tests': {
         'mixins': [
-          'linux-xenial',
+          'linux-focal',
           'x86-64',
         ],
         'test_suites': {
diff --git a/testing/scripts/wpt_common.py b/testing/scripts/wpt_common.py
index cc9281c..d978012f 100644
--- a/testing/scripts/wpt_common.py
+++ b/testing/scripts/wpt_common.py
@@ -362,7 +362,7 @@
         if self.wptreport:
             command.extend(['--wpt-report',
                             self.wptreport])
-        common.run_command(command)
+        return common.run_command(command)
 
     def clean_up_after_test_run(self):
         if self._include_filename:
diff --git a/testing/test.gni b/testing/test.gni
index cd37d02d..2be7351 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -494,9 +494,6 @@
     }
 
     use_v2_script = use_v2_script_default
-    if (target_cpu == "x64") {
-      use_v2_script = false
-    }
     if (defined(invoker.fuchsia_legacy_script_required) &&
         invoker.fuchsia_legacy_script_required) {
       use_v2_script = false
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1419be8..b024a2b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2011,31 +2011,6 @@
             ]
         }
     ],
-    "BrokeringDiskCacheOnAndroid": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "OutOfProcessNetworkService",
-                    "disable_features": [
-                        "BrokerFileOperationsOnDiskCacheInNetworkService",
-                        "NetworkServiceInProcess2"
-                    ]
-                },
-                {
-                    "name": "BrokerDiskCache",
-                    "enable_features": [
-                        "BrokerFileOperationsOnDiskCacheInNetworkService"
-                    ],
-                    "disable_features": [
-                        "NetworkServiceInProcess2"
-                    ]
-                }
-            ]
-        }
-    ],
     "BrowserPeriodicYieldingToNative": [
         {
             "platforms": [
@@ -5695,37 +5670,6 @@
             ]
         }
     ],
-    "IOSNewOverflowMenu": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NewOverflowMenu"
-                    ]
-                }
-            ]
-        }
-    ],
-    "IOSNewOverflowMenuExperiments": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "BothActions",
-                    "enable_features": [
-                        "NewOverflowMenuCBDAction",
-                        "NewOverflowMenuSettingsAction"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSNewOverflowMenuIPH": [
         {
             "platforms": [
@@ -6527,6 +6471,44 @@
             ]
         }
     ],
+    "LocalWebApprovals": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "PreferLocal_481152618",
+                    "params": {
+                        "preferred_button": "local"
+                    },
+                    "enable_features": [
+                        "LocalWebApprovals",
+                        "WebFilterInterstitialRefresh"
+                    ]
+                },
+                {
+                    "name": "PreferRemote_481152618",
+                    "params": {
+                        "preferred_button": "remote"
+                    },
+                    "enable_features": [
+                        "LocalWebApprovals",
+                        "WebFilterInterstitialRefresh"
+                    ]
+                },
+                {
+                    "name": "RemoteOnly_481152618",
+                    "enable_features": [
+                        "WebFilterInterstitialRefresh"
+                    ],
+                    "disable_features": [
+                        "LocalWebApprovals"
+                    ]
+                }
+            ]
+        }
+    ],
     "LowerMemoryLimitForNonMainRenderers": [
         {
             "platforms": [
@@ -7805,6 +7787,26 @@
             ]
         }
     ],
+    "OriginAgentClusterDefaultEnabled": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "OriginAgentClusterDefaultEnabled"
+                    ]
+                }
+            ]
+        }
+    ],
     "OutOfProcessSystemDnsResolution": [
         {
             "platforms": [
@@ -12161,6 +12163,25 @@
             ]
         }
     ],
+    "WebContentsCaptureHiDPI": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "WebContentsCaptureHiDPI"
+                    ]
+                }
+            ]
+        }
+    ],
     "WebFeedsMVP": [
         {
             "platforms": [
diff --git a/third_party/blink/common/notifications/notification_mojom_traits.cc b/third_party/blink/common/notifications/notification_mojom_traits.cc
index f6ee5e7..2fa1944 100644
--- a/third_party/blink/common/notifications/notification_mojom_traits.cc
+++ b/third_party/blink/common/notifications/notification_mojom_traits.cc
@@ -66,7 +66,8 @@
       !notification_data.ReadActions(&platform_notification_data->actions) ||
       !notification_data.ReadData(&data) ||
       !notification_data.ReadShowTriggerTimestamp(
-          &platform_notification_data->show_trigger_timestamp)) {
+          &platform_notification_data->show_trigger_timestamp) ||
+      !notification_data.ReadScenario(&platform_notification_data->scenario)) {
     return false;
   }
 
diff --git a/third_party/blink/common/notifications/notification_mojom_traits_unittest.cc b/third_party/blink/common/notifications/notification_mojom_traits_unittest.cc
index 34b6b446..0a7a634 100644
--- a/third_party/blink/common/notifications/notification_mojom_traits_unittest.cc
+++ b/third_party/blink/common/notifications/notification_mojom_traits_unittest.cc
@@ -55,6 +55,7 @@
   notification_data.silent = true;
   notification_data.require_interaction = true;
   notification_data.show_trigger_timestamp = base::Time::Now();
+  notification_data.scenario = mojom::NotificationScenario::INCOMING_CALL;
 
   const char data[] = "mock binary notification data";
   notification_data.data.assign(data, data + std::size(data));
@@ -115,6 +116,7 @@
   }
   EXPECT_EQ(roundtrip_notification_data.show_trigger_timestamp,
             notification_data.show_trigger_timestamp);
+  EXPECT_EQ(roundtrip_notification_data.scenario, notification_data.scenario);
 }
 
 // Check upper bound on vibration entries (99).
diff --git a/third_party/blink/common/notifications/platform_notification_data.cc b/third_party/blink/common/notifications/platform_notification_data.cc
index b838e21..16a6813 100644
--- a/third_party/blink/common/notifications/platform_notification_data.cc
+++ b/third_party/blink/common/notifications/platform_notification_data.cc
@@ -8,7 +8,8 @@
 namespace blink {
 
 PlatformNotificationData::PlatformNotificationData()
-    : direction(mojom::NotificationDirection::LEFT_TO_RIGHT) {}
+    : direction(mojom::NotificationDirection::LEFT_TO_RIGHT),
+      scenario(mojom::NotificationScenario::DEFAULT) {}
 
 PlatformNotificationData::PlatformNotificationData(
     const PlatformNotificationData& other) {
@@ -37,6 +38,7 @@
   for (auto& action : other.actions)
     actions.push_back(action.Clone());
   show_trigger_timestamp = other.show_trigger_timestamp;
+  scenario = other.scenario;
 
   return *this;
 }
diff --git a/third_party/blink/perf_tests/base64/atob-whitespace.html b/third_party/blink/perf_tests/base64/atob-whitespace.html
index 8acc2f6..fb36458 100644
--- a/third_party/blink/perf_tests/base64/atob-whitespace.html
+++ b/third_party/blink/perf_tests/base64/atob-whitespace.html
@@ -5,15 +5,15 @@
 <script>
 var longStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ";
 for (var i = 0; i < 15; i++)
-    longStr = longStr + longStr;
+  longStr = longStr + longStr;
 
 PerfTestRunner.measureRunsPerSecond({
-    description: "This benchmark covers `atob` where the input has whitespace.",
-    setup: function() {
-    },
-    run: function() {
-      atob(longStr);
-}});
+description: "This benchmark covers `atob` where the input has whitespace.",
+  setup: function() {},
+  run: function() {
+    atob(longStr);
+  }
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/perf_tests/base64/atob.html b/third_party/blink/perf_tests/base64/atob.html
index 0cbe62a8..4ba7d902 100644
--- a/third_party/blink/perf_tests/base64/atob.html
+++ b/third_party/blink/perf_tests/base64/atob.html
@@ -5,15 +5,15 @@
 <script>
 var longStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 for (var i = 0; i < 15; i++)
-    longStr = longStr + longStr;
+  longStr = longStr + longStr;
 
 PerfTestRunner.measureRunsPerSecond({
-    description: "This benchmark covers `atob`.",
-    setup: function() {
-    },
-    run: function() {
-      atob(longStr);
-}});
+description: "This benchmark covers `atob`.",
+  setup: function() {},
+  run: function() {
+    atob(longStr);
+  }
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/perf_tests/base64/btoa.html b/third_party/blink/perf_tests/base64/btoa.html
index 7c14760..5346a89 100644
--- a/third_party/blink/perf_tests/base64/btoa.html
+++ b/third_party/blink/perf_tests/base64/btoa.html
@@ -5,16 +5,16 @@
 <script>
 var longStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 for (var i = 0; i < 15; i++)
-    longStr = longStr + longStr;
+  longStr = longStr + longStr;
 var binary = atob(longStr);
 
 PerfTestRunner.measureRunsPerSecond({
-    description: "This benchmark covers `btoa`.",
-    setup: function() {
-    },
-    run: function() {
-      btoa(binary);
-}});
+description: "This benchmark covers `btoa`.",
+  setup: function() {},
+  run: function() {
+    btoa(binary);
+  }
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/public/common/notifications/notification_mojom_traits.h b/third_party/blink/public/common/notifications/notification_mojom_traits.h
index b358eac..22af574 100644
--- a/third_party/blink/public/common/notifications/notification_mojom_traits.h
+++ b/third_party/blink/public/common/notifications/notification_mojom_traits.h
@@ -100,6 +100,11 @@
     return data.show_trigger_timestamp;
   }
 
+  static blink::mojom::NotificationScenario scenario(
+      const blink::PlatformNotificationData& data) {
+    return data.scenario;
+  }
+
   static bool Read(blink::mojom::NotificationDataDataView notification_data,
                    blink::PlatformNotificationData* platform_notification_data);
 };
diff --git a/third_party/blink/public/common/notifications/platform_notification_data.h b/third_party/blink/public/common/notifications/platform_notification_data.h
index 43a3394..a541e69 100644
--- a/third_party/blink/public/common/notifications/platform_notification_data.h
+++ b/third_party/blink/public/common/notifications/platform_notification_data.h
@@ -80,6 +80,8 @@
 
   // The time at which the notification should be shown.
   absl::optional<base::Time> show_trigger_timestamp;
+
+  mojom::NotificationScenario scenario;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/mojom/notifications/notification.mojom b/third_party/blink/public/mojom/notifications/notification.mojom
index 6aa8b4d..bd002fc 100644
--- a/third_party/blink/public/mojom/notifications/notification.mojom
+++ b/third_party/blink/public/mojom/notifications/notification.mojom
@@ -22,6 +22,12 @@
   TEXT
 };
 
+// Scenarios that define the notification behavior.
+enum NotificationScenario {
+  DEFAULT,
+  INCOMING_CALL
+};
+
 // Structure representing an action button associated with a Notification.
 struct NotificationAction {
   // Type of action this structure represents.
@@ -107,6 +113,13 @@
 
   // Time when to show this notification.
   mojo_base.mojom.Time? show_trigger_timestamp;
+
+  // The notification scenario can be either DEFAULT or INCOMING_CALL. If the
+  // latter was selected and the Notification was triggered by an installed web
+  // app, it should have a ringtone, customized action buttons, and increased
+  // priority. See the proposal's explainer at:
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Notifications/notifications_actions_customization.md
+  NotificationScenario scenario = DEFAULT;
 };
 
 // Structure representing the resources associated with a Web Notification.
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index d3eabc0..1177bcc 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1304,6 +1304,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_direction.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_permission.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_permission.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_scenario.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_scenario.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_offscreen_rendering_context_type.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_offscreen_rendering_context_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_opus_bitstream_format.cc",
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index ba14466..20564a3 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -783,11 +783,15 @@
   if (removed)
     ApplyEffects();
 
+  auto property_pass_filter = [](const PropertyHandle& property) {
+    return property.IsCSSProperty();
+  };
+
   ActiveInterpolationsMap results = EffectStack::ActiveInterpolations(
       &target()->GetElementAnimations()->GetEffectStack(),
       /*new_animations=*/nullptr,
-      /*suppressed_animations=*/nullptr, kDefaultPriority,
-      /*property_pass_filter=*/nullptr, this);
+      /*suppressed_animations=*/nullptr, kDefaultPriority, property_pass_filter,
+      this);
 
   if (removed)
     ClearEffects();
diff --git a/third_party/blink/renderer/core/css/invalidation/style_invalidator.cc b/third_party/blink/renderer/core/css/invalidation/style_invalidator.cc
index 818338af..26bb444 100644
--- a/third_party/blink/renderer/core/css/invalidation/style_invalidator.cc
+++ b/third_party/blink/renderer/core/css/invalidation/style_invalidator.cc
@@ -295,14 +295,6 @@
     if (UNLIKELY(element.NeedsStyleInvalidation()))
       PushInvalidationSetsForContainerNode(element, sibling_data);
 
-    // When a slot element is invalidated, the slotted elements are also
-    // invalidated by HTMLSlotElement::DidRecalcStyle. So if WholeSubtreeInvalid
-    // is true, they will be included even though they are not part of the
-    // subtree. It's not necessary to fully recalc style for the slotted
-    // elements in that case as they just need to pick up changed inherited
-    // styles but we do it. If we ever stop doing that then this code and the
-    // PushInvalidationSetsForContainerNode above need to move out of the
-    // if-block.
     auto* html_slot_element = DynamicTo<HTMLSlotElement>(element);
     if (html_slot_element && InvalidatesSlotted())
       InvalidateSlotDistributedElements(*html_slot_element);
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 4cf21ba..3c66539 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -2970,11 +2970,18 @@
     const StyleRecalcContext& style_recalc_context) {
   DCHECK(GetDocument().InStyleRecalc());
 
-  // FIXME: Instead of clearing updates that may have been added from calls to
-  // ResolveStyle outside RecalcStyle, we should just never set them if we're
-  // not inside RecalcStyle.
-  if (ElementAnimations* element_animations = GetElementAnimations())
+  if (ElementAnimations* element_animations = GetElementAnimations()) {
+    // For multiple style recalc passes for the same element in the same
+    // lifecycle, which can happen for container queries, we may end up having
+    // pending updates from the previous pass. In that case the update from the
+    // previous pass should be dropped as it will be re-added if necessary. It
+    // may be that an update detected in the previous pass would no longer be
+    // necessary if the animated property flipped back to the old style with no
+    // change as the result.
+    DCHECK(GetDocument().GetStyleEngine().InContainerQueryStyleRecalc() ||
+           element_animations->CssAnimations().PendingUpdate().IsEmpty());
     element_animations->CssAnimations().ClearPendingUpdate();
+  }
 
   scoped_refptr<ComputedStyle> style =
       HasCustomStyleCallbacks()
diff --git a/third_party/blink/renderer/core/html/forms/resources/color_picker.js b/third_party/blink/renderer/core/html/forms/resources/color_picker.js
index 3237527..6b2fa7d 100644
--- a/third_party/blink/renderer/core/html/forms/resources/color_picker.js
+++ b/third_party/blink/renderer/core/html/forms/resources/color_picker.js
@@ -442,12 +442,13 @@
         this.systemColorPicker_, this.colorValueAXAnnouncer_);
 
     this.visualColorPicker_.addEventListener(
-        'visual-color-picker-initialized', this.initializeListeners_);
+        'visual-color-picker-initialized',
+        this.onVisualColorPickerInitialized_);
 
     window.addEventListener('resize', this.onWindowResize_, {once: true});
   }
 
-  initializeListeners_ = () => {
+  onVisualColorPickerInitialized_ = () => {
     this.manualColorPicker_
         .addEventListener('manual-color-change', this.onManualColorChange_);
 
@@ -460,7 +461,11 @@
     window.addEventListener('message', this.onMessageReceived_);
 
     document.documentElement.addEventListener('keydown', this.onKeyDown_);
-  }
+
+    // Announce color now as any fired visual-color-change event would not have
+    // been caught by the listener as it was just added in this method.
+    this.colorValueAXAnnouncer_.announceColor(this.selectedColor);
+  };
 
   get selectedColor() {
     return this.selectedColor_;
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index c8e3543..8fb15ca2 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1491,14 +1491,16 @@
 
   GetPopoverData()->setInvoker(nullptr);
   GetPopoverData()->setNeedsRepositioningForSelectMenu(false);
-  // Stop matching `:open`:
-  GetPopoverData()->setVisibilityState(PopoverVisibilityState::kTransitioning);
-  PseudoStateChanged(CSSSelector::kPseudoOpen);
 
   // Fire the popoverhide event (bubbles, not cancelable).
   Event* event = Event::CreateBubble(event_type_names::kPopoverhide);
   event->SetTarget(this);
   if (force_hide) {
+    // Stop matching `:open` now:
+    GetPopoverData()->setVisibilityState(
+        PopoverVisibilityState::kTransitioning);
+    PseudoStateChanged(CSSSelector::kPseudoOpen);
+
     // We will be force-hidden when the popover element is being removed from
     // the document, during which event dispatch is prohibited.
     GetDocument().EnqueueAnimationFrameEvent(event);
@@ -1508,6 +1510,10 @@
   auto result = DispatchEvent(*event);
   DCHECK_EQ(result, DispatchEventResult::kNotCanceled);
 
+  // Stop matching `:open`:
+  GetPopoverData()->setVisibilityState(PopoverVisibilityState::kTransitioning);
+  PseudoStateChanged(CSSSelector::kPseudoOpen);
+
   // The 'popoverhide' event handler could have changed this popover, e.g. by
   // changing its type, removing it from the document, or calling showPopover().
   if (!isConnected() || !HasPopoverAttribute() ||
diff --git a/third_party/blink/renderer/core/html/html_image_element.h b/third_party/blink/renderer/core/html/html_image_element.h
index fe6beeb..f54ca4fe 100644
--- a/third_party/blink/renderer/core/html/html_image_element.h
+++ b/third_party/blink/renderer/core/html/html_image_element.h
@@ -100,11 +100,14 @@
   ImageResourceContent* CachedImage() const {
     return GetImageLoader().GetContent();
   }
-  void LoadDeferredImage() {
-    GetImageLoader().LoadDeferredImage(referrer_policy_);
+  void LoadDeferredImageFromMicrotask() {
+    GetImageLoader().LoadDeferredImage(referrer_policy_,
+                                       /*force_blocking*/ false,
+                                       /*update_from_microtask*/ true);
   }
   void LoadDeferredImageBlockingLoad() {
-    GetImageLoader().LoadDeferredImage(referrer_policy_, true);
+    GetImageLoader().LoadDeferredImage(referrer_policy_,
+                                       /*force_blocking*/ true);
   }
   void SetImageForTest(ImageResourceContent* content) {
     GetImageLoader().SetImageForTest(content);
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
index c7f05e9..6bc5e75 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
@@ -224,14 +224,14 @@
         // Check that style was null because it was not computed since the
         // element was in an invisible subtree.
         DCHECK(style || IsElementInInvisibleSubTree(*element));
-        image_element->LoadDeferredImage();
+        image_element->LoadDeferredImageFromMicrotask();
         lazy_load_intersection_observer_->unobserve(element);
       }
     }
     if (!entry->isIntersecting())
       continue;
     if (image_element)
-      image_element->LoadDeferredImage();
+      image_element->LoadDeferredImageFromMicrotask();
 
     // Load the background image if the element has one deferred.
     if (const ComputedStyle* style = element->GetComputedStyle())
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index ca5eae0..897f36d 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -674,7 +674,8 @@
     delay_until_do_update_from_element_ = nullptr;
   }
 
-  if (ShouldLoadImmediately(ImageSourceToKURL(image_source_url))) {
+  if (ShouldLoadImmediately(ImageSourceToKURL(image_source_url)) &&
+      update_behavior != kUpdateFromMicrotask) {
     DoUpdateFromElement(element_->GetExecutionContext()->GetCurrentWorld(),
                         update_behavior, referrer_policy, UpdateType::kSync,
                         force_blocking);
@@ -960,7 +961,8 @@
 
 void ImageLoader::LoadDeferredImage(
     network::mojom::ReferrerPolicy referrer_policy,
-    bool force_blocking) {
+    bool force_blocking,
+    bool update_from_microtask) {
   if (lazy_image_load_state_ != LazyImageLoadState::kDeferred)
     return;
   DCHECK(!image_complete_);
@@ -968,7 +970,9 @@
 
   // If the image has been fully deferred (no placeholder fetch), report it as
   // fully loaded now.
-  UpdateFromElement(kUpdateNormal, referrer_policy, force_blocking);
+  UpdateFromElement(
+      update_from_microtask ? kUpdateFromMicrotask : kUpdateNormal,
+      referrer_policy, force_blocking);
 }
 
 void ImageLoader::ElementDidMoveToNewDocument() {
diff --git a/third_party/blink/renderer/core/loader/image_loader.h b/third_party/blink/renderer/core/loader/image_loader.h
index 1714e7d..50bc6a9 100644
--- a/third_party/blink/renderer/core/loader/image_loader.h
+++ b/third_party/blink/renderer/core/loader/image_loader.h
@@ -61,6 +61,10 @@
     // document, or when DOM mutations trigger a new load. Starts loading if a
     // load hasn't already been started.
     kUpdateNormal,
+    // This is the behavior when the update is triggered by the lazy loading
+    // mechanism. We can't update synchronously, because doing so may invalidate
+    // style, which is forbidden from lazy load callbacks.
+    kUpdateFromMicrotask,
     // This should be the update behavior when the resource was changed (via
     // 'src', 'srcset' or 'sizes'). Starts a new load even if a previous load of
     // the same resource have failed, to match Firefox's behavior.
@@ -142,7 +146,8 @@
 
   // force_blocking ensures that the image will block the load event.
   void LoadDeferredImage(network::mojom::ReferrerPolicy,
-                         bool force_blocking = false);
+                         bool force_blocking = false,
+                         bool update_from_microtask = false);
 
  protected:
   void ImageChanged(ImageResourceContent*, CanDeferInvalidation) override;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 02990a5..d9adf34c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -2969,18 +2969,6 @@
   SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
 #endif  // DCHECK_IS_ON()
 
-  if (event_type == ax::mojom::blink::Event::kChildrenChanged &&
-      obj->CachedParentObject()) {
-    const bool was_ignored = obj->LastKnownIsIgnoredValue();
-    const bool was_in_tree = obj->LastKnownIsIncludedInTreeValue();
-    obj->UpdateCachedAttributeValuesIfNeeded(false);
-    const bool is_ignored = obj->LastKnownIsIgnoredValue();
-    const bool is_in_tree = obj->LastKnownIsIncludedInTreeValue();
-
-    if (is_ignored != was_ignored || was_in_tree != is_in_tree)
-      ChildrenChangedWithCleanLayout(obj->CachedParentObject());
-  }
-
   PostPlatformNotification(obj, event_type, event_from, event_from_action,
                            event_intents);
 }
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.cc b/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.cc
index 49ee4a9..fdf7bd0 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.cc
@@ -21,6 +21,7 @@
 #include "third_party/blink/renderer/core/fileapi/blob.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_error.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_writable_file_stream.h"
+#include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
@@ -67,8 +68,9 @@
 ScriptPromise FileSystemUnderlyingSink::close(ScriptState* script_state,
                                               ExceptionState& exception_state) {
   if (!writer_remote_.is_bound() || pending_operation_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Object reached an invalid state");
+    ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                       DOMExceptionCode::kInvalidStateError,
+                                       "Object reached an invalid state");
     return ScriptPromise();
   }
   pending_operation_ =
@@ -97,8 +99,8 @@
     ExceptionState& exception_state) {
   if (params.type() == "truncate") {
     if (!params.hasSizeNonNull()) {
-      exception_state.ThrowDOMException(
-          DOMExceptionCode::kSyntaxError,
+      ThrowDOMExceptionAndInvalidateSink(
+          exception_state, DOMExceptionCode::kSyntaxError,
           "Invalid params passed. truncate requires a size argument");
       return ScriptPromise();
     }
@@ -107,8 +109,8 @@
 
   if (params.type() == "seek") {
     if (!params.hasPositionNonNull()) {
-      exception_state.ThrowDOMException(
-          DOMExceptionCode::kSyntaxError,
+      ThrowDOMExceptionAndInvalidateSink(
+          exception_state, DOMExceptionCode::kSyntaxError,
           "Invalid params passed. seek requires a position argument");
       return ScriptPromise();
     }
@@ -119,21 +121,23 @@
     uint64_t position =
         params.hasPositionNonNull() ? params.positionNonNull() : offset_;
     if (!params.hasData()) {
-      exception_state.ThrowDOMException(
-          DOMExceptionCode::kSyntaxError,
+      ThrowDOMExceptionAndInvalidateSink(
+          exception_state, DOMExceptionCode::kSyntaxError,
           "Invalid params passed. write requires a data argument");
       return ScriptPromise();
     }
     if (!params.data()) {
-      exception_state.ThrowTypeError(
+      ThrowTypeErrorAndInvalidateSink(
+          exception_state,
           "Invalid params passed. write requires a non-null data");
       return ScriptPromise();
     }
     return WriteData(script_state, position, params.data(), exception_state);
   }
 
-  exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                    "Object reached an invalid state");
+  ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                     DOMExceptionCode::kInvalidStateError,
+                                     "Object reached an invalid state");
   return ScriptPromise();
 }
 
@@ -313,16 +317,26 @@
   options.capacity_num_bytes = BlobUtils::GetDataPipeCapacity(data_size);
 
   MojoResult rv = CreateDataPipe(&options, producer, consumer);
-  if (rv != MOJO_RESULT_OK) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Failed to create datapipe");
-    return false;
-  }
-  return true;
+  return rv == MOJO_RESULT_OK;
 }
 
 }  // namespace
 
+void FileSystemUnderlyingSink::ThrowDOMExceptionAndInvalidateSink(
+    ExceptionState& exception_state,
+    DOMExceptionCode error,
+    const char* message) {
+  exception_state.ThrowDOMException(error, message);
+  writer_remote_.reset();
+}
+
+void FileSystemUnderlyingSink::ThrowTypeErrorAndInvalidateSink(
+    ExceptionState& exception_state,
+    const char* message) {
+  exception_state.ThrowTypeError(message);
+  writer_remote_.reset();
+}
+
 ScriptPromise FileSystemUnderlyingSink::WriteData(
     ScriptState* script_state,
     uint64_t position,
@@ -331,8 +345,9 @@
   DCHECK(data);
 
   if (!writer_remote_.is_bound() || pending_operation_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Object reached an invalid state");
+    ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                       DOMExceptionCode::kInvalidStateError,
+                                       "Object reached an invalid state");
     return ScriptPromise();
   }
 
@@ -380,6 +395,9 @@
   mojo::ScopedDataPipeConsumerHandle consumer_handle;
   if (!CreateDataPipe(data_size, exception_state, producer_handle,
                       consumer_handle)) {
+    ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                       DOMExceptionCode::kInvalidStateError,
+                                       "Failed to create datapipe");
     return ScriptPromise();
   }
 
@@ -422,8 +440,9 @@
     uint64_t size,
     ExceptionState& exception_state) {
   if (!writer_remote_.is_bound() || pending_operation_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Object reached an invalid state");
+    ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                       DOMExceptionCode::kInvalidStateError,
+                                       "Object reached an invalid state");
     return ScriptPromise();
   }
   pending_operation_ =
@@ -439,8 +458,9 @@
                                              uint64_t offset,
                                              ExceptionState& exception_state) {
   if (!writer_remote_.is_bound() || pending_operation_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Object reached an invalid state");
+    ThrowDOMExceptionAndInvalidateSink(exception_state,
+                                       DOMExceptionCode::kInvalidStateError,
+                                       "Object reached an invalid state");
     return ScriptPromise();
   }
   offset_ = offset;
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.h b/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.h
index 303847c..cb6f3f6 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_writer.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/core/streams/underlying_sink_base.h"
+#include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 
@@ -40,6 +41,16 @@
   void Trace(Visitor*) const override;
 
  private:
+  // Helpers which ensure `writer_remote_` is reset when there's an error.
+  // A WritableStream becomes unusable once there's been an error on the stream.
+  // Resetting the remote destroys the corresponding receiver, thereby releasing
+  // any locks tied to the writer.
+  void ThrowDOMExceptionAndInvalidateSink(ExceptionState& exception_state,
+                                          DOMExceptionCode error,
+                                          const char* message);
+  void ThrowTypeErrorAndInvalidateSink(ExceptionState& exception_state,
+                                       const char* message);
+
   ScriptPromise HandleParams(ScriptState*, const WriteParams&, ExceptionState&);
   ScriptPromise WriteData(
       ScriptState*,
diff --git a/third_party/blink/renderer/modules/notifications/notification.cc b/third_party/blink/renderer/modules/notifications/notification.cc
index 79e0c76..9f61a874 100644
--- a/third_party/blink/renderer/modules/notifications/notification.cc
+++ b/third_party/blink/renderer/modules/notifications/notification.cc
@@ -395,6 +395,18 @@
   return result;
 }
 
+String Notification::scenario() const {
+  switch (data_->scenario) {
+    case mojom::blink::NotificationScenario::DEFAULT:
+      return "default";
+    case mojom::blink::NotificationScenario::INCOMING_CALL:
+      return "incoming-call";
+  }
+
+  NOTREACHED();
+  return String();
+}
+
 String Notification::PermissionString(
     mojom::blink::PermissionStatus permission) {
   switch (permission) {
diff --git a/third_party/blink/renderer/modules/notifications/notification.h b/third_party/blink/renderer/modules/notifications/notification.h
index 2d20d7c..c4c9f9a40 100644
--- a/third_party/blink/renderer/modules/notifications/notification.h
+++ b/third_party/blink/renderer/modules/notifications/notification.h
@@ -118,6 +118,7 @@
   ScriptValue data(ScriptState* script_state);
   Vector<v8::Local<v8::Value>> actions(ScriptState* script_state) const;
   TimestampTrigger* showTrigger() const { return show_trigger_; }
+  String scenario() const;
 
   static String PermissionString(mojom::blink::PermissionStatus permission);
   static String permission(ExecutionContext* context);
diff --git a/third_party/blink/renderer/modules/notifications/notification.idl b/third_party/blink/renderer/modules/notifications/notification.idl
index d7604ec..96375619 100644
--- a/third_party/blink/renderer/modules/notifications/notification.idl
+++ b/third_party/blink/renderer/modules/notifications/notification.idl
@@ -74,6 +74,7 @@
     [CallWith=ScriptState, SameObject, SaveSameObject] readonly attribute any data;
     [CallWith=ScriptState, SameObject, SaveSameObject] readonly attribute FrozenArray<NotificationAction> actions;
     [RuntimeEnabled=NotificationTriggers, SameObject] readonly attribute TimestampTrigger showTrigger;
+    [RuntimeEnabled=IncomingCallNotifications] readonly attribute DOMString scenario;
 
     [MeasureAs=NotificationClosed] void close();
 };
diff --git a/third_party/blink/renderer/modules/notifications/notification_data.cc b/third_party/blink/renderer/modules/notifications/notification_data.cc
index 504ee05..271741b 100644
--- a/third_party/blink/renderer/modules/notifications/notification_data.cc
+++ b/third_party/blink/renderer/modules/notifications/notification_data.cc
@@ -36,6 +36,15 @@
   return mojom::blink::NotificationDirection::AUTO;
 }
 
+mojom::blink::NotificationScenario ToScenarioEnumValue(const String& scenario) {
+  if (scenario == "default")
+    return mojom::blink::NotificationScenario::DEFAULT;
+  if (scenario == "incoming-call")
+    return mojom::blink::NotificationScenario::INCOMING_CALL;
+  NOTREACHED() << "Unknown scenario: " << scenario;
+  return mojom::blink::NotificationScenario::DEFAULT;
+}
+
 KURL CompleteURL(ExecutionContext* context, const String& string_url) {
   KURL url = context->CompleteURL(string_url);
   if (url.IsValid())
@@ -183,6 +192,8 @@
     notification_data->show_trigger_timestamp = timestamp;
   }
 
+  notification_data->scenario = ToScenarioEnumValue(options->scenario());
+
   return notification_data;
 }
 
diff --git a/third_party/blink/renderer/modules/notifications/notification_options.idl b/third_party/blink/renderer/modules/notifications/notification_options.idl
index f8a8ff7..84dd631 100644
--- a/third_party/blink/renderer/modules/notifications/notification_options.idl
+++ b/third_party/blink/renderer/modules/notifications/notification_options.idl
@@ -10,6 +10,11 @@
     "rtl"
 };
 
+enum NotificationScenario {
+    "default",
+    "incoming-call"
+};
+
 dictionary NotificationOptions {
     NotificationDirection dir = "auto";
     DOMString lang = "";
@@ -27,4 +32,5 @@
     any data = null;
     sequence<NotificationAction> actions = [];
     [RuntimeEnabled=NotificationTriggers] TimestampTrigger showTrigger;
+    [RuntimeEnabled=IncomingCallNotifications] NotificationScenario scenario = "default";
 };
diff --git a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.cc b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.cc
index a4196e5..7d3de33 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.cc
@@ -23,8 +23,10 @@
  * DAMAGE.
  */
 
-#include <memory>
 #include "third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.h"
+
+#include <memory>
+
 #include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.h b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.h
index 6f4d60f..c17195a1 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_BASIC_PROCESSOR_HANDLER_H_
 
 #include <memory>
+
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
index 1d32341..5933e32 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_buffer.h"
 
 #include <memory>
+
 #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_buffer_options.h"
 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc b/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
index 7d9385f..ddf0abfc 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/modules/webaudio/audio_node.h"
+#include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
 
 #include <memory>
 
@@ -10,7 +10,7 @@
 #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/testing/dummy_page_holder.h"
-#include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
+#include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node_wiring.h"
 #include "third_party/blink/renderer/modules/webaudio/delay_node.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.h b/third_party/blink/renderer/modules/webaudio/audio_param.h
index 7a84fabd..e4b832d 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_param.h
@@ -30,6 +30,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_H_
 
 #include <sys/types.h>
+
 #include <atomic>
 
 #include "base/memory/scoped_refptr.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param_handler.h b/third_party/blink/renderer/modules/webaudio/audio_param_handler.h
index 26c2537d..9258b65 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_param_handler.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_HANDLER_H_
 
 #include <sys/types.h>
+
 #include <atomic>
 
 #include "base/memory/scoped_refptr.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param_timeline.h b/third_party/blink/renderer/modules/webaudio/audio_param_timeline.h
index 3e0ffa2..770738010 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param_timeline.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_param_timeline.h
@@ -29,6 +29,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_TIMELINE_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_TIMELINE_H_
 
+#include <tuple>
+
 #include "base/synchronization/lock.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_destination_node.h"
@@ -37,8 +39,6 @@
 #include "third_party/blink/renderer/platform/wtf/threading.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
-#include <tuple>
-
 namespace blink {
 
 class AudioParamTimeline {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_summing_junction.cc b/third_party/blink/renderer/modules/webaudio/audio_summing_junction.cc
index 5d4e129c0..d1d7479 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_summing_junction.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_summing_junction.cc
@@ -23,10 +23,12 @@
  * DAMAGE.
  */
 
-#include <algorithm>
-#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_summing_junction.h"
 
+#include <algorithm>
+
+#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
+
 namespace blink {
 
 AudioSummingJunction::AudioSummingJunction(DeferredTaskHandler& handler)
diff --git a/third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.cc b/third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.cc
index 5c4999f..ac8f548a 100644
--- a/third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.cc
+++ b/third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.cc
@@ -23,8 +23,10 @@
  * DAMAGE.
  */
 
-#include <limits.h>
 #include "third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.h"
+
+#include <limits.h>
+
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
diff --git a/third_party/blink/renderer/modules/webaudio/biquad_processor.cc b/third_party/blink/renderer/modules/webaudio/biquad_processor.cc
index 8853a6c..2fc8252 100644
--- a/third_party/blink/renderer/modules/webaudio/biquad_processor.cc
+++ b/third_party/blink/renderer/modules/webaudio/biquad_processor.cc
@@ -23,11 +23,12 @@
  * DAMAGE.
  */
 
+#include "third_party/blink/renderer/modules/webaudio/biquad_processor.h"
+
 #include <memory>
 
 #include "base/synchronization/lock.h"
 #include "third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.h"
-#include "third_party/blink/renderer/modules/webaudio/biquad_processor.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/webaudio/biquad_processor.h b/third_party/blink/renderer/modules/webaudio/biquad_processor.h
index 8dec01f..967c43bd 100644
--- a/third_party/blink/renderer/modules/webaudio/biquad_processor.h
+++ b/third_party/blink/renderer/modules/webaudio/biquad_processor.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_BIQUAD_PROCESSOR_H_
 
 #include <memory>
+
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_param.h"
diff --git a/third_party/blink/renderer/modules/webaudio/cpu/arm/oscillator_kernel_neon.cc b/third_party/blink/renderer/modules/webaudio/cpu/arm/oscillator_kernel_neon.cc
index b0811bd..060f19ab 100644
--- a/third_party/blink/renderer/modules/webaudio/cpu/arm/oscillator_kernel_neon.cc
+++ b/third_party/blink/renderer/modules/webaudio/cpu/arm/oscillator_kernel_neon.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/modules/webaudio/oscillator_node.h"
+#include "third_party/blink/renderer/modules/webaudio/oscillator_handler.h"
 
 #include "third_party/blink/renderer/modules/webaudio/periodic_wave.h"
 
diff --git a/third_party/blink/renderer/modules/webaudio/cpu/x86/oscillator_kernel_sse2.cc b/third_party/blink/renderer/modules/webaudio/cpu/x86/oscillator_kernel_sse2.cc
index bbb099b..b12df79 100644
--- a/third_party/blink/renderer/modules/webaudio/cpu/x86/oscillator_kernel_sse2.cc
+++ b/third_party/blink/renderer/modules/webaudio/cpu/x86/oscillator_kernel_sse2.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/modules/webaudio/oscillator_node.h"
-
-#include "third_party/blink/renderer/modules/webaudio/periodic_wave.h"
+#include "third_party/blink/renderer/modules/webaudio/oscillator_handler.h"
 
 #include <xmmintrin.h>
 
+#include "third_party/blink/renderer/modules/webaudio/periodic_wave.h"
+
 namespace blink {
 
 namespace {
diff --git a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
index 6ad98e8f..212613e 100644
--- a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_DEFERRED_TASK_HANDLER_H_
 
 #include <atomic>
+
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
diff --git a/third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.cc b/third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.cc
index e41ae1d..e479fbd 100644
--- a/third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.cc
+++ b/third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.cc
@@ -23,8 +23,10 @@
  * DAMAGE.
  */
 
-#include <algorithm>
 #include "third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.h"
+
+#include <algorithm>
+
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
diff --git a/third_party/blink/renderer/modules/webaudio/delay_processor.cc b/third_party/blink/renderer/modules/webaudio/delay_processor.cc
index 5b278bde..0426142 100644
--- a/third_party/blink/renderer/modules/webaudio/delay_processor.cc
+++ b/third_party/blink/renderer/modules/webaudio/delay_processor.cc
@@ -23,9 +23,11 @@
  * DAMAGE.
  */
 
-#include <memory>
-#include "third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.h"
 #include "third_party/blink/renderer/modules/webaudio/delay_processor.h"
+
+#include <memory>
+
+#include "third_party/blink/renderer/modules/webaudio/delay_dsp_kernel.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/webaudio/delay_processor.h b/third_party/blink/renderer/modules/webaudio/delay_processor.h
index 59f58ae..197c307 100644
--- a/third_party/blink/renderer/modules/webaudio/delay_processor.h
+++ b/third_party/blink/renderer/modules/webaudio/delay_processor.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_DELAY_PROCESSOR_H_
 
 #include <memory>
+
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_param.h"
 #include "third_party/blink/renderer/platform/audio/audio_dsp_kernel_processor.h"
diff --git a/third_party/blink/renderer/modules/webaudio/iir_processor.cc b/third_party/blink/renderer/modules/webaudio/iir_processor.cc
index 755062fe..41ad752 100644
--- a/third_party/blink/renderer/modules/webaudio/iir_processor.cc
+++ b/third_party/blink/renderer/modules/webaudio/iir_processor.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/webaudio/iir_processor.h"
 
 #include <memory>
+
 #include "third_party/blink/renderer/modules/webaudio/iir_dsp_kernel.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/webaudio/iir_processor.h b/third_party/blink/renderer/modules/webaudio/iir_processor.h
index 0383b4c..868e60a3 100644
--- a/third_party/blink/renderer/modules/webaudio/iir_processor.h
+++ b/third_party/blink/renderer/modules/webaudio/iir_processor.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_IIR_PROCESSOR_H_
 
 #include <memory>
+
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 #include "third_party/blink/renderer/platform/audio/audio_dsp_kernel.h"
 #include "third_party/blink/renderer/platform/audio/audio_dsp_kernel_processor.h"
diff --git a/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.cc b/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.cc
index d1a0f8c..ce1eb7a2 100644
--- a/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.cc
+++ b/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h"
 
 #include <memory>
+
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_context.h"
diff --git a/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h b/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h
index 9945d039..136686b 100644
--- a/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h
+++ b/third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_INSPECTOR_WEB_AUDIO_AGENT_H_
 
 #include <memory>
+
 #include "third_party/blink/renderer/core/inspector/inspector_base_agent.h"
 #include "third_party/blink/renderer/core/inspector/protocol/web_audio.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
diff --git a/third_party/blink/renderer/modules/webaudio/periodic_wave.cc b/third_party/blink/renderer/modules/webaudio/periodic_wave.cc
index d28b8f6..c39e832 100644
--- a/third_party/blink/renderer/modules/webaudio/periodic_wave.cc
+++ b/third_party/blink/renderer/modules/webaudio/periodic_wave.cc
@@ -26,6 +26,8 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "third_party/blink/renderer/modules/webaudio/periodic_wave.h"
+
 #include <algorithm>
 #include <memory>
 
@@ -33,7 +35,6 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_periodic_wave_options.h"
 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
 #include "third_party/blink/renderer/modules/webaudio/oscillator_node.h"
-#include "third_party/blink/renderer/modules/webaudio/periodic_wave.h"
 #include "third_party/blink/renderer/platform/audio/fft_frame.h"
 #include "third_party/blink/renderer/platform/audio/vector_math.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
diff --git a/third_party/blink/renderer/modules/webaudio/periodic_wave.h b/third_party/blink/renderer/modules/webaudio/periodic_wave.h
index 5a5bbd9b..cafc329 100644
--- a/third_party/blink/renderer/modules/webaudio/periodic_wave.h
+++ b/third_party/blink/renderer/modules/webaudio/periodic_wave.h
@@ -30,6 +30,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_PERIODIC_WAVE_H_
 
 #include <memory>
+
 #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
 #include "third_party/blink/renderer/platform/audio/audio_array.h"
diff --git a/third_party/blink/renderer/modules/webaudio/wave_shaper_dsp_kernel.h b/third_party/blink/renderer/modules/webaudio/wave_shaper_dsp_kernel.h
index 35addcbd..0eaf8cf 100644
--- a/third_party/blink/renderer/modules/webaudio/wave_shaper_dsp_kernel.h
+++ b/third_party/blink/renderer/modules/webaudio/wave_shaper_dsp_kernel.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_WAVE_SHAPER_DSP_KERNEL_H_
 
 #include <memory>
+
 #include "third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h"
 #include "third_party/blink/renderer/platform/audio/audio_array.h"
 #include "third_party/blink/renderer/platform/audio/audio_dsp_kernel.h"
diff --git a/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.cc b/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.cc
index 8f65095c..d4554fd 100644
--- a/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.cc
+++ b/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.cc
@@ -23,11 +23,12 @@
  * DAMAGE.
  */
 
+#include "third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h"
+
 #include <memory>
 
 #include "base/synchronization/lock.h"
 #include "third_party/blink/renderer/modules/webaudio/wave_shaper_dsp_kernel.h"
-#include "third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h b/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h
index 939d780f..b8fc6ea53 100644
--- a/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h
+++ b/third_party/blink/renderer/modules/webaudio/wave_shaper_processor.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_WAVE_SHAPER_PROCESSOR_H_
 
 #include <memory>
+
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
diff --git a/third_party/blink/renderer/platform/p2p/ipc_network_manager.cc b/third_party/blink/renderer/platform/p2p/ipc_network_manager.cc
index a2834d3..d33f0629 100644
--- a/third_party/blink/renderer/platform/p2p/ipc_network_manager.cc
+++ b/third_party/blink/renderer/platform/p2p/ipc_network_manager.cc
@@ -130,8 +130,8 @@
       underlying_adapter_type = adapter_type;
       adapter_type = rtc::ADAPTER_TYPE_VPN;
     }
-    auto network = std::make_unique<rtc::Network>(
-        it->name, it->name, prefix, it->prefix_length, adapter_type);
+    auto network = CreateNetwork(it->name, it->name, prefix, it->prefix_length,
+                                 adapter_type);
     if (adapter_type == rtc::ADAPTER_TYPE_VPN) {
       network->set_underlying_type_for_vpn(underlying_adapter_type);
     }
@@ -176,8 +176,8 @@
   if (Platform::Current()->AllowsLoopbackInPeerConnection()) {
     std::string name_v4("loopback_ipv4");
     rtc::IPAddress ip_address_v4(INADDR_LOOPBACK);
-    auto network_v4 = std::make_unique<rtc::Network>(
-        name_v4, name_v4, ip_address_v4, 32, rtc::ADAPTER_TYPE_UNKNOWN);
+    auto network_v4 = CreateNetwork(name_v4, name_v4, ip_address_v4, 32,
+                                    rtc::ADAPTER_TYPE_UNKNOWN);
     network_v4->set_default_local_address_provider(this);
     network_v4->set_mdns_responder_provider(this);
     network_v4->AddIP(ip_address_v4);
@@ -191,8 +191,8 @@
       DCHECK(!ipv6_default_address.IsNil());
       std::string name_v6("loopback_ipv6");
       rtc::IPAddress ip_address_v6(in6addr_loopback);
-      auto network_v6 = std::make_unique<rtc::Network>(
-          name_v6, name_v6, ip_address_v6, 64, rtc::ADAPTER_TYPE_UNKNOWN);
+      auto network_v6 = CreateNetwork(name_v6, name_v6, ip_address_v6, 64,
+                                      rtc::ADAPTER_TYPE_UNKNOWN);
       network_v6->set_default_local_address_provider(this);
       network_v6->set_mdns_responder_provider(this);
       network_v6->AddIP(ip_address_v6);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 83d847c..1f705433 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1477,6 +1477,10 @@
       status: {"Android": "stable"},
     },
     {
+      name: "IncomingCallNotifications",
+      base_feature: "IncomingCallNotifications",
+    },
+    {
       name: "InertAttribute",
       status: "stable",
     },
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
index 80b1c98..aa490ec5 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
@@ -618,7 +618,7 @@
         return results
 
     def _worker_factory(self, worker_connection):
-        return Worker(worker_connection, self._tool.git().checkout_root)
+        return Worker(self._tool.git().checkout_root)
 
     def rebaseline(self, options, test_baseline_set):
         """Fetches new baselines and removes related test expectation lines.
@@ -827,28 +827,27 @@
         self.rebaseline(options, test_baseline_set)
 
 
-class Worker(object):
-    def __init__(self, caller, cwd):
+class Worker:
+    """A worker delegate for running Blink tool commands.
 
-        # Add the header here to avoid the circle import
-        from blinkpy.tool.blink_tool import BlinkTool
+    The purpose of this worker is to persist HTTP(S) connections to ResultDB
+    across command invocations.
 
-        self._caller = caller
-        self._tool = BlinkTool(cwd)
+    See Also:
+        crbug.com/1213998#c50
+    """
 
-    def __del__(self):
-        self.stop()
+    def __init__(self, cwd: str):
+        self._cwd = cwd
 
     def start(self):
-        """This method is called when the object is starting to be used and it is safe
-        for the object to create state that does not need to be pickled (usually this means
-        it is called in a child process).
-        """
-        pass
+        # Dynamically import `BlinkTool` to avoid a circular import.
+        from blinkpy.tool.blink_tool import BlinkTool
+        # `BlinkTool` cannot be serialized, so construct one here in the worker
+        # process instead of in the constructor, which runs in the managing
+        # process. See crbug.com/1386267.
+        self._tool = BlinkTool(self._cwd)
 
     def handle(self, name, source, command):
         assert name == 'rebaseline'
         self._tool.main(command[1:])
-
-    def stop(self):
-        _log.debug('%s cleaning up', self._caller.name)
diff --git a/third_party/blink/tools/run_wpt_tests.py b/third_party/blink/tools/run_wpt_tests.py
index a97f277..dd5bd50 100755
--- a/third_party/blink/tools/run_wpt_tests.py
+++ b/third_party/blink/tools/run_wpt_tests.py
@@ -12,6 +12,7 @@
 import shutil
 import sys
 
+from blinkpy.common import exit_codes
 from blinkpy.common import path_finder
 from blinkpy.common.path_finder import PathFinder
 from blinkpy.web_tests.port.android import (
@@ -259,8 +260,10 @@
             json.dump(run_info, file_handle)
 
     def do_post_test_run_tasks(self):
-        self.process_and_upload_results()
-        if self.options.show_results_in_browser:
+        process_return = self.process_and_upload_results()
+
+        if (process_return != exit_codes.INTERRUPTED_EXIT_STATUS
+                and self.options.show_results_in_browser):
             self.show_results_in_browser()
 
     def show_results_in_browser(self):
@@ -870,8 +873,12 @@
     # This environment fix is needed on windows as codec is trying
     # to encode in cp1252 rather than utf-8 and throwing errors.
     os.environ['PYTHONUTF8'] = '1'
-    adapter = WPTAdapter()
-    return adapter.run_test()
+
+    try:
+        adapter = WPTAdapter()
+        return adapter.run_test()
+    except KeyboardInterrupt:
+        return exit_codes.INTERRUPTED_EXIT_STATUS
 
 
 # This is not really a "script test" so does not need to manually add
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index 801837537..8b3d678 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -58,6 +58,9 @@
 # Sheriff 2022-08-04
 crbug.com/1350279 [ Linux ] external/wpt/accessibility/crashtests/svg-mouse-listener.html [ Failure Pass ]
 
+# This has been marked as Skip in the TestExpectations file.
+crbug.com/1385278 [ Linux ] external/wpt/resource-timing/iframe-failed-commit.html [ Skip ]
+
 ###########################################################################
 # WARNING: Memory leaks must be fixed asap. Sheriff is expected to revert #
 # culprit CLs instead of suppressing the leaks. If you have any question, #
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7e09e35..9460f63 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3131,7 +3131,9 @@
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html [ Timeout ]
-crbug.com/626703 [ Linux ] external/wpt/resource-timing/object-not-found-adds-entry.html [ Timeout ]
+crbug.com/626703 external/wpt/resource-timing/object-not-found-adds-entry.html [ Timeout Failure Pass ]
+crbug.com/1385265 virtual/plz-dedicated-worker/external/wpt/resource-timing/object-not-found-adds-entry.html [ Skip Failure Pass ]
+crbug.com/1385278 external/wpt/resource-timing/iframe-failed-commit.html [ Skip Failure Pass Timeout ]
 crbug.com/626703 external/wpt/webrtc/RTCConfiguration-iceTransportPolicy.html [ Skip Timeout ]
 crbug.com/626703 [ Mac12 ] external/wpt/mediacapture-record/MediaRecorder-mimetype.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.14 ] virtual/portals/external/wpt/portals/no-portal-in-sandboxed-popup.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any-expected.txt b/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any-expected.txt
deleted file mode 100644
index 4abcb23..0000000
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL removeEntry() while the file has an open writable fails assert_unreached: Should have rejected: undefined Reached unreachable code
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.js.ini b/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.js.ini
deleted file mode 100644
index d3bc3cb..0000000
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.js.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[FileSystemDirectoryHandle-removeEntry.https.any.worker.html]
-  [removeEntry() while the file has an open writable fails]
-    expected: FAIL
-
-
-[FileSystemDirectoryHandle-removeEntry.https.any.html]
-  [removeEntry() while the file has an open writable fails]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.worker-expected.txt
deleted file mode 100644
index 4abcb23..0000000
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemDirectoryHandle-removeEntry.https.any.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL removeEntry() while the file has an open writable fails assert_unreached: Should have rejected: undefined Reached unreachable code
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
index 108b9135..93b03ef 100644
--- a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
+++ b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
@@ -4,15 +4,120 @@
   const handle =
       await createFileWithContents(t, 'file-to-remove', '12345', root);
   await createFileWithContents(t, 'file-to-keep', 'abc', root);
+  await root.removeEntry('file-to-remove');
+
+  assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+  await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'removeEntry() to remove a file');
+
+directory_test(async (t, root) => {
+  const handle =
+      await createFileWithContents(t, 'file-to-remove', '12345', root);
+  await root.removeEntry('file-to-remove');
+
+  await promise_rejects_dom(
+      t, 'NotFoundError', root.removeEntry('file-to-remove'));
+}, 'removeEntry() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+  const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+  await createFileWithContents(t, 'file-to-keep', 'abc', root);
+  await root.removeEntry('dir-to-remove');
+
+  assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() to remove an empty directory');
+
+directory_test(async (t, root) => {
+  const dir = await createDirectory(t, 'dir-to-remove', root);
+  await createFileWithContents(t, 'file-in-dir', 'abc', dir);
+
+  await promise_rejects_dom(
+      t, 'InvalidModificationError', root.removeEntry('dir-to-remove'));
+  assert_array_equals(
+      await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+  assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'removeEntry() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+  // root
+  // ├──file-to-keep
+  // ├──dir-to-remove
+  //    ├── file0
+  //    ├── dir1-in-dir
+  //    │   └── file1
+  //    └── dir2
+  const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+  await createFileWithContents(t, 'file-to-keep', 'abc', root);
+  await createEmptyFile(t, 'file0', dir);
+  const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+  await createEmptyFile(t, 'file1', dir1_in_dir);
+  await createDirectory(t, 'dir2-in-dir', dir);
+
+  await root.removeEntry('dir-to-remove', {recursive: true});
+  assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+  const dir = await createDirectory(t, 'dir', root);
+  await promise_rejects_js(t, TypeError, dir.removeEntry(''));
+}, 'removeEntry() with empty name should fail');
+
+directory_test(async (t, root) => {
+  const dir = await createDirectory(t, 'dir', root);
+  await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory));
+}, `removeEntry() with "${kCurrentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+  const dir = await createDirectory(t, 'dir', root);
+  await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory));
+}, `removeEntry() with "${kParentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+  const dir_name = 'dir-name';
+  const dir = await createDirectory(t, dir_name, root);
+
+  const file_name = 'file-name';
+  await createEmptyFile(t, file_name, dir);
+
+  for (let i = 0; i < kPathSeparators.length; ++i) {
+    const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`;
+    await promise_rejects_js(
+        t, TypeError, root.removeEntry(path_with_separator),
+        `removeEntry() must reject names containing "${kPathSeparators[i]}"`);
+  }
+}, 'removeEntry() with a path separator should fail.');
+
+directory_test(async (t, root) => {
+  const handle =
+      await createFileWithContents(t, 'file-to-remove', '12345', root);
+  await createFileWithContents(t, 'file-to-keep', 'abc', root);
 
   const writable = await cleanup_writable(t, await handle.createWritable());
   await promise_rejects_dom(
-    t, 'InvalidModificationError', root.removeEntry('file-to-remove'));
+      t, 'NoModificationAllowedError', root.removeEntry('file-to-remove'));
 
   await writable.close();
   await root.removeEntry('file-to-remove');
 
-  assert_array_equals(
-      await getSortedDirectoryEntries(root),
-      ['file-to-keep']);
+  assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
 }, 'removeEntry() while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+  const dir_name = 'dir-name';
+  const dir = await createDirectory(t, dir_name, root);
+
+  const handle =
+      await createFileWithContents(t, 'file-to-remove', '12345', dir);
+  await createFileWithContents(t, 'file-to-keep', 'abc', dir);
+
+  const writable = await cleanup_writable(t, await handle.createWritable());
+  await promise_rejects_dom(
+      t, 'NoModificationAllowedError', root.removeEntry(dir_name));
+
+  await writable.close();
+  assert_array_equals(
+      await getSortedDirectoryEntries(dir), ['file-to-keep', 'file-to-remove']);
+
+  await dir.removeEntry('file-to-remove');
+  assert_array_equals(await getSortedDirectoryEntries(dir), ['file-to-keep']);
+}, 'removeEntry() of a directory while a containing file has an open writable fails');
diff --git a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream-write.js b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream-write.js
index 70ba72f..43c8ec7 100644
--- a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream-write.js
+++ b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream-write.js
@@ -195,17 +195,6 @@
 }, 'write() with a valid typed array buffer');
 
 directory_test(async (t, root) => {
-  const dir = await createDirectory(t, 'parent_dir', root);
-  const file_name = 'close_fails_when_dir_removed.txt';
-  const handle = await createEmptyFile(t, file_name, dir);
-  const stream = await handle.createWritable();
-  await stream.write('foo');
-
-  await root.removeEntry('parent_dir', {recursive: true});
-  await promise_rejects_dom(t, 'NotFoundError', stream.close());
-}, 'atomic writes: close() fails when parent directory is removed');
-
-directory_test(async (t, root) => {
   const handle = await createEmptyFile(t, 'atomic_writes.txt', root);
   const stream = await handle.createWritable();
   await stream.write('foox');
@@ -277,22 +266,6 @@
 }, 'atomic writes: only one close() operation may succeed');
 
 directory_test(async (t, root) => {
-  const dir = await createDirectory(t, 'parent_dir', root);
-  const file_name = 'atomic_writable_file_stream_persists_removed.txt';
-  const handle = await createFileWithContents(t, file_name, 'foo', dir);
-
-  const stream = await handle.createWritable();
-  await stream.write('bar');
-
-  await dir.removeEntry(file_name);
-  await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
-
-  await stream.close();
-  assert_equals(await getFileContents(handle), 'bar');
-  assert_equals(await getFileSize(handle), 3);
-}, 'atomic writes: writable file stream persists file on close, even if file is removed');
-
-directory_test(async (t, root) => {
   const handle = await createEmptyFile(t, 'writer_written', root);
   const stream = await handle.createWritable();
   assert_false(stream.locked);
@@ -315,8 +288,8 @@
   const stream = await handle.createWritable();
 
   await promise_rejects_dom(
-      t, "SyntaxError", stream.write({type: 'truncate'}), 'truncate without size');
-
+      t, 'SyntaxError', stream.write({type: 'truncate'}),
+      'truncate without size');
 }, 'WriteParams: truncate missing size param');
 
 directory_test(async (t, root) => {
@@ -324,8 +297,7 @@
   const stream = await handle.createWritable();
 
   await promise_rejects_dom(
-      t, "SyntaxError", stream.write({type: 'write'}), 'write without data');
-
+      t, 'SyntaxError', stream.write({type: 'write'}), 'write without data');
 }, 'WriteParams: write missing data param');
 
 directory_test(async (t, root) => {
@@ -333,18 +305,17 @@
   const stream = await handle.createWritable();
 
   await promise_rejects_js(
-      t, TypeError, stream.write({type: 'write', data: null}), 'write with null data');
-
+      t, TypeError, stream.write({type: 'write', data: null}),
+      'write with null data');
 }, 'WriteParams: write null data param');
 
 directory_test(async (t, root) => {
-  const handle = await createFileWithContents(
-      t, 'content.txt', 'seekable', root);
+  const handle =
+      await createFileWithContents(t, 'content.txt', 'seekable', root);
   const stream = await handle.createWritable();
 
   await promise_rejects_dom(
-      t, "SyntaxError", stream.write({type: 'seek'}), 'seek without position');
-
+      t, 'SyntaxError', stream.write({type: 'seek'}), 'seek without position');
 }, 'WriteParams: seek missing position param');
 
 directory_test(async (t, root) => {
diff --git a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream.js b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream.js
index d9c4f35..53e4fc1 100644
--- a/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream.js
+++ b/third_party/blink/web_tests/external/wpt/fs/script-tests/FileSystemWritableFileStream.js
@@ -34,26 +34,6 @@
 }, 'createWritable() fails when parent directory is removed');
 
 directory_test(async (t, root) => {
-  const dir = await createDirectory(t, 'parent_dir', root);
-  const file_name = 'write_fails_when_dir_removed.txt';
-  const handle = await createEmptyFile(t, file_name, dir);
-  const stream = await handle.createWritable();
-
-  await root.removeEntry('parent_dir', {recursive: true});
-  await promise_rejects_dom(t, 'NotFoundError', stream.write('foo'));
-}, 'write() fails when parent directory is removed');
-
-directory_test(async (t, root) => {
-  const dir = await createDirectory(t, 'parent_dir', root);
-  const file_name = 'truncate_fails_when_dir_removed.txt';
-  const handle = await createEmptyFile(t, file_name, dir);
-  const stream = await handle.createWritable();
-
-  await root.removeEntry('parent_dir', {recursive: true});
-  await promise_rejects_dom(t, 'NotFoundError', stream.truncate(0));
-}, 'truncate() fails when parent directory is removed');
-
-directory_test(async (t, root) => {
   const handle = await createFileWithContents(
       t, 'atomic_file_is_copied.txt', 'fooks', root);
   const stream = await handle.createWritable({keepExistingData: true});
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.tentative.html
index 2f530d1..78d4a22 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.tentative.html
@@ -17,21 +17,31 @@
       assert_false(popover.matches(':open'));
       let showCount = 0;
       let hideCount = 0;
+      function showListener(e) {
+        assert_true(e.target.matches(':closed'),'The popover should be in the :closed state when the popovershow event fires.');
+        assert_false(e.target.matches(':open'),'The popover should *not* be in the :open state when the popovershow event fires.');
+        ++showCount;
+      };
+      function hideListener(e) {
+        assert_true(e.target.matches(':open'),'The popover should be in the :open state when the popoverhide event fires.');
+        assert_false(e.target.matches(':closed'),'The popover should *not* be in the :closed state when the popoverhide event fires.');
+        ++hideCount;
+      };
       switch (method) {
         case "listener":
           const controller = new AbortController();
           const signal = controller.signal;
           t.add_cleanup(() => controller.abort());
-          document.addEventListener('popovershow',() => ++showCount, {signal});
-          document.addEventListener('popoverhide',() => ++hideCount, {signal});
+          document.addEventListener('popovershow',showListener, {signal});
+          document.addEventListener('popoverhide',hideListener, {signal});
           break;
         case "attribute":
           assert_false(popover.hasAttribute('onpopovershow'));
           assert_false(popover.hasAttribute('onpopoverhide'));
           t.add_cleanup(() => popover.removeAttribute('onpopovershow'));
           t.add_cleanup(() => popover.removeAttribute('onpopoverhide'));
-          popover.onpopovershow = () => ++showCount;
-          popover.onpopoverhide = () => ++hideCount;
+          popover.onpopovershow = showListener;
+          popover.onpopoverhide = hideListener;
           break;
         default: assert_unreached();
       }
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/commitStyles-svg-crash.html b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/commitStyles-svg-crash.html
new file mode 100644
index 0000000..7fc1fef
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/commitStyles-svg-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class=test-wait>
+<link rel=help href="https://crbug.com/1385691">
+<svg id=svg></svg>
+<script>
+  let anim = svg.animate({'svg-viewBox': '1 1 1 1'}, 1);
+  anim.ready.then(() => {
+    anim.commitStyles();
+    document.documentElement.classList.remove('test-wait');
+  });
+</script>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/web-vitals.html b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/web-vitals.html
index b54346b..195d845 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/web-vitals.html
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/web-vitals.html
@@ -2,6 +2,7 @@
 <html>
   <body>
     <script>
+      (new PerformanceObserver(console.log)).observe({ entryTypes: ["layout-shift"] });
       window.setTimeout(() => {
         const img = document.querySelector('img');
         img.setAttribute('src', './big-image.png');
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt
deleted file mode 100644
index 2fabc08..0000000
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt
+++ /dev/null
@@ -1,147 +0,0 @@
-Tests the data of page load and web vitals trace events
-Recording started
-Tracing complete
-Got SetLayerTreeId event:
-Object: {
-	args: {
-		data: {
-			frame: string
-			layerTreeId: number
-		}
-	}
-	cat: string
-	name: string
-	ph: string
-	pid: number
-	s: string
-	tid: number
-	ts: number
-	tts: number
-}
-Got BeginFrame event:
-Array: [
-	{
-		args: {
-			frameSeqId: number
-			layerTreeId: number
-		}
-		cat: string
-		name: string
-		ph: string
-		pid: number
-		s: string
-		tid: number
-		ts: number
-	}
-]
-Got DrawFrame event:
-Object: {
-	args: {
-		frameSeqId: number
-		layerTreeId: number
-	}
-	cat: string
-	name: string
-	ph: string
-	pid: number
-	s: string
-	tid: number
-	ts: number
-	tts: number
-}
-Got ActivateLayerTree event:
-Object: {
-	args: {
-		frameId: number
-		layerTreeId: number
-	}
-	cat: string
-	name: string
-	ph: string
-	pid: number
-	s: string
-	tid: number
-	ts: number
-	tts: number
-}
-Got NeedsBeginFrameChanged event:
-Object: {
-	args: {
-		data: {
-			needsBeginFrame: number
-		}
-		layerTreeId: number
-	}
-	cat: string
-	name: string
-	ph: string
-	pid: number
-	s: string
-	tid: number
-	ts: number
-	tts: number
-}
-Got BeginMainThreadFrame event:
-Object: {
-	args: {
-		data: {
-			frameId: number
-		}
-		layerTreeId: number
-	}
-	cat: string
-	name: string
-	ph: string
-	pid: number
-	s: string
-	tid: number
-	ts: number
-	tts: number
-}
-Got Paint event:
-Object: {
-	args: {
-		data: {
-			clip: [
-				number,
-				number,
-				number,
-				number,
-				number,
-				number,
-				number,
-				number,
-			]
-			frame: string
-			layerId: number
-			nodeId: number
-		}
-	}
-	cat: string
-	dur: number
-	name: string
-	ph: string
-	pid: number
-	tdur: number
-	tid: number
-	ts: number
-	tts: number
-}
-Got CompositeLayers event:
-Object: {
-	args: {
-		frameSeqId: number
-		layerTreeId: number
-	}
-	cat: string
-	dur: number
-	name: string
-	ph: string
-	pid: number
-	tdur: number
-	tid: number
-	ts: number
-	tts: number
-}
-All screenshots have image data.
-
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle.js b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle.js
deleted file mode 100644
index 5212c34b..0000000
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle.js
+++ /dev/null
@@ -1,94 +0,0 @@
-(async function(testRunner) {
-  const {session, dp} = await testRunner.startBlank(
-      'Tests the data of page load and web vitals trace events');
-
-  const TracingHelper =
-      await testRunner.loadScript('../resources/tracing-test.js');
-  const tracingHelper = new TracingHelper(testRunner, session);
-  await tracingHelper.startTracing(
-      'disabled-by-default-devtools.timeline,disabled-by-default-devtools.timeline.frame,devtools.timeline,loading');
-  await dp.Page.navigate({
-    url: 'http://127.0.0.1:8000/inspector-protocol/resources/web-vitals.html'
-  });
-
-  // Wait for trace events.
-  const largestContentfulPaintPromise = await session.evaluateAsync(`
-      (function() {
-        let resolvePromiseCallback = () => ({});
-        const observerCallBack = entryList => {
-          const entries = entryList.getEntries();
-          for (const entry of entries) {
-            if (entry.element.tagName === 'IMG' && entry.element.getAttribute('src', './big-image.png')) {
-              resolvePromiseCallback();
-            }
-          }
-        };
-        const observer = new PerformanceObserver(observerCallBack);
-        const largestContentfulPaintPromise = new Promise((res) => {resolvePromiseCallback = res})
-        observer.observe({entryTypes: ['largest-contentful-paint']});
-        return largestContentfulPaintPromise;
-      })()
-    `);
-  await largestContentfulPaintPromise;
-  await tracingHelper.stopTracing(
-      /loading|(disabled-by-default)?devtools.timeline(.frame)?|rail/);
-
-  const drawFrame = tracingHelper.findEvents('DrawFrame', 'I').at(-1);
-
-  function hasExpectedFrameSeqId(event) {
-    return event.args.frameSeqId === drawFrame.args.frameSeqId;
-  }
-  function hasExpectedLayerTreeId(event) {
-    return event.args.layerTreeId === drawFrame.args.layerTreeId;
-  }
-
-  // Given a DrawFrame event, find the events marking the other steps
-  // of that frame's life cycle.
-
-  const beginFrame =
-      tracingHelper.findEvents('BeginFrame', 'I', hasExpectedFrameSeqId);
-  const compositeLayers =
-      tracingHelper.findEvent('CompositeLayers', 'X', hasExpectedFrameSeqId);
-  const activateLayerTree =
-      tracingHelper.findEvent('ActivateLayerTree', 'I', hasExpectedLayerTreeId);
-  const needsBeginFrameChanged = tracingHelper.findEvent(
-      'NeedsBeginFrameChanged', 'I', hasExpectedLayerTreeId);
-  const beginMainThreadFrame = tracingHelper.findEvent(
-      'BeginMainThreadFrame', 'I', hasExpectedLayerTreeId);
-
-  // Other trace events in the frame domain that do not necessarily
-  // belong to the life cycle of the DrawFrame event used above.
- const setLayerTreeId = tracingHelper.findEvent('SetLayerTreeId', 'I');
-  const paint = tracingHelper.findEvent('Paint', 'X');
-  const screenshots = tracingHelper.findEvents('Screenshot', 'O');
-
-  testRunner.log('Got SetLayerTreeId event:');
-  tracingHelper.logEventShape(setLayerTreeId)
-
-  testRunner.log('Got BeginFrame event:');
-  tracingHelper.logEventShape(beginFrame)
-
-  testRunner.log('Got DrawFrame event:');
-  tracingHelper.logEventShape(drawFrame)
-
-  testRunner.log('Got ActivateLayerTree event:');
-  tracingHelper.logEventShape(activateLayerTree)
-
-  testRunner.log('Got NeedsBeginFrameChanged event:');
-  tracingHelper.logEventShape(needsBeginFrameChanged)
-
-  testRunner.log('Got BeginMainThreadFrame event:');
-  tracingHelper.logEventShape(beginMainThreadFrame)
-
-  testRunner.log('Got Paint event:');
-  tracingHelper.logEventShape(paint)
-
-  testRunner.log('Got CompositeLayers event:');
-  tracingHelper.logEventShape(compositeLayers);
-
-  if (screenshots && screenshots.every(s => s.args.snapshot)) {
-    testRunner.log('All screenshots have image data.');
-  }
-
-  testRunner.completeTest();
-})
\ No newline at end of file
diff --git a/third_party/boringssl/BUILD.generated.gni b/third_party/boringssl/BUILD.generated.gni
index 71fed77b..ce4eddb 100644
--- a/third_party/boringssl/BUILD.generated.gni
+++ b/third_party/boringssl/BUILD.generated.gni
@@ -22,7 +22,6 @@
   "src/crypto/asn1/a_time.c",
   "src/crypto/asn1/a_type.c",
   "src/crypto/asn1/a_utctm.c",
-  "src/crypto/asn1/a_utf8.c",
   "src/crypto/asn1/asn1_lib.c",
   "src/crypto/asn1/asn1_par.c",
   "src/crypto/asn1/asn_pack.c",
@@ -76,10 +75,12 @@
   "src/crypto/conf/conf_def.h",
   "src/crypto/conf/internal.h",
   "src/crypto/cpu_aarch64_apple.c",
+  "src/crypto/cpu_aarch64_freebsd.c",
   "src/crypto/cpu_aarch64_fuchsia.c",
   "src/crypto/cpu_aarch64_linux.c",
   "src/crypto/cpu_aarch64_win.c",
   "src/crypto/cpu_arm.c",
+  "src/crypto/cpu_arm_freebsd.c",
   "src/crypto/cpu_arm_linux.c",
   "src/crypto/cpu_arm_linux.h",
   "src/crypto/cpu_intel.c",
diff --git a/third_party/boringssl/apple-aarch64/crypto/fipsmodule/p256-armv8-asm.S b/third_party/boringssl/apple-aarch64/crypto/fipsmodule/p256-armv8-asm.S
index 7a5202dd..75d2b93 100644
--- a/third_party/boringssl/apple-aarch64/crypto/fipsmodule/p256-armv8-asm.S
+++ b/third_party/boringssl/apple-aarch64/crypto/fipsmodule/p256-armv8-asm.S
@@ -1402,7 +1402,7 @@
 
 ////////////////////////////////////////////////////////////////////////
 // void ecp_nistz256_ord_sqr_mont(uint64_t res[4], uint64_t a[4],
-//                                int rep);
+//                                uint64_t rep);
 .globl	_ecp_nistz256_ord_sqr_mont
 .private_extern	_ecp_nistz256_ord_sqr_mont
 
diff --git a/third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S b/third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S
index 3efcccb6..d255da2 100644
--- a/third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S
+++ b/third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S
@@ -1403,7 +1403,7 @@
 
 ////////////////////////////////////////////////////////////////////////
 // void ecp_nistz256_ord_sqr_mont(uint64_t res[4], uint64_t a[4],
-//                                int rep);
+//                                uint64_t rep);
 .globl	ecp_nistz256_ord_sqr_mont
 .hidden	ecp_nistz256_ord_sqr_mont
 .type	ecp_nistz256_ord_sqr_mont,%function
diff --git a/third_party/boringssl/win-aarch64/crypto/fipsmodule/p256-armv8-asm.S b/third_party/boringssl/win-aarch64/crypto/fipsmodule/p256-armv8-asm.S
index cfffc0d..d84e536c 100644
--- a/third_party/boringssl/win-aarch64/crypto/fipsmodule/p256-armv8-asm.S
+++ b/third_party/boringssl/win-aarch64/crypto/fipsmodule/p256-armv8-asm.S
@@ -1437,7 +1437,7 @@
 
 ////////////////////////////////////////////////////////////////////////
 // void ecp_nistz256_ord_sqr_mont(uint64_t res[4], uint64_t a[4],
-//                                int rep);
+//                                uint64_t rep);
 .globl	ecp_nistz256_ord_sqr_mont
 
 .def ecp_nistz256_ord_sqr_mont
diff --git a/third_party/libgav1/BUILD.gn b/third_party/libgav1/BUILD.gn
index 78f53e8..97f5affc 100644
--- a/third_party/libgav1/BUILD.gn
+++ b/third_party/libgav1/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//build/config/arm.gni")
-import("//third_party/libgav1/libgav1_srcs.gni")
 import("//third_party/libgav1/options.gni")
 
 config("public_libgav1_config") {
@@ -39,69 +38,43 @@
   }
 }
 
-# TODO(b/259317395): Add source_set for parsing only.
 if (use_libgav1_parser) {
-  # Separate from libgav1 because utils/constants.cc and dsp/constants.cc
-  # generate the same object file, constants.o.
-  source_set("libgav1_utils") {
+  static_library("libgav1_parser") {
     configs -= [ "//build/config/compiler:chromium_code" ]
     configs += [ "//build/config/compiler:no_chromium_code" ]
     configs += [ ":private_libgav1_config" ]
 
     public_configs = [ ":public_libgav1_config" ]
 
-    sources = gav1_utils_sources
-  }
-
-  # Separate from libgav1 because film_grain.cc and dsp/film_grain.cc
-  # generate the same object file, film_grain.o.
-  source_set("libgav1_dsp") {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-    configs += [ ":private_libgav1_config" ]
-
-    deps = [
-      ":libgav1_dsp_sse4",
-      ":libgav1_utils",
+    sources = [
+      "//third_party/libgav1/src/src/buffer_pool.cc",
+      "//third_party/libgav1/src/src/buffer_pool.h",
+      "//third_party/libgav1/src/src/frame_buffer.cc",
+      "//third_party/libgav1/src/src/internal_frame_buffer_list.cc",
+      "//third_party/libgav1/src/src/internal_frame_buffer_list.h",
+      "//third_party/libgav1/src/src/obu_parser.cc",
+      "//third_party/libgav1/src/src/obu_parser.h",
+      "//third_party/libgav1/src/src/quantizer.cc",
+      "//third_party/libgav1/src/src/quantizer.h",
+      "//third_party/libgav1/src/src/status_code.cc",
+      "//third_party/libgav1/src/src/symbol_decoder_context.cc",
+      "//third_party/libgav1/src/src/symbol_decoder_context.h",
+      "//third_party/libgav1/src/src/utils/bit_reader.cc",
+      "//third_party/libgav1/src/src/utils/bit_reader.h",
+      "//third_party/libgav1/src/src/utils/constants.cc",
+      "//third_party/libgav1/src/src/utils/constants.h",
+      "//third_party/libgav1/src/src/utils/logging.cc",
+      "//third_party/libgav1/src/src/utils/logging.h",
+      "//third_party/libgav1/src/src/utils/raw_bit_reader.cc",
+      "//third_party/libgav1/src/src/utils/raw_bit_reader.h",
+      "//third_party/libgav1/src/src/utils/segmentation.cc",
+      "//third_party/libgav1/src/src/utils/segmentation.h",
+      "//third_party/libgav1/src/src/utils/segmentation_map.cc",
+      "//third_party/libgav1/src/src/utils/segmentation_map.h",
+      "//third_party/libgav1/src/src/warp_prediction.cc",
+      "//third_party/libgav1/src/src/warp_prediction.h",
+      "//third_party/libgav1/src/src/yuv_buffer.cc",
+      "//third_party/libgav1/src/src/yuv_buffer.h",
     ]
-    public_configs = [ ":public_libgav1_config" ]
-
-    sources = gav1_dsp_sources + gav1_dsp_headers_sources
-    sources += gav1_dsp_avx2_sources + gav1_dsp_avx2_headers_sources
-  }
-
-  # SSE4 sources are split to their own target as Chrome is currently built
-  # with -msse3.
-  source_set("libgav1_dsp_sse4") {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-    configs += [ ":private_libgav1_config" ]
-
-    deps = [ ":libgav1_utils" ]
-    public_configs = [ ":public_libgav1_config" ]
-
-    if (current_cpu == "x86" || current_cpu == "x64") {
-      cflags = [ "-msse4.1" ]
-    }
-
-    sources = gav1_dsp_sse4_sources + gav1_dsp_sse4_headers_sources +
-              gav1_dsp_headers_sources + gav1_dsp_avx2_headers_sources
-  }
-
-  static_library("libgav1") {
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [ "//build/config/compiler:no_chromium_code" ]
-    configs += [ ":private_libgav1_config" ]
-
-    public_configs = [ ":public_libgav1_config" ]
-    public_deps = [
-      ":libgav1_dsp",
-      ":libgav1_utils",
-    ]
-
-    sources = gav1_common_sources
-    sources += gav1_gav1_sources
-    sources += gav1_post_filter_sources
-    sources += gav1_tile_sources
   }
 }
diff --git a/third_party/libgav1/README.chromium b/third_party/libgav1/README.chromium
index 733e246..2323a4b 100644
--- a/third_party/libgav1/README.chromium
+++ b/third_party/libgav1/README.chromium
@@ -22,6 +22,5 @@
 
   Use the generated commit message for the roll.
 
-2. Generate .gni and update Date and Commit in README.chromium
-  cd third_party/libgav1
-  go run generate_libgav1_src_gni.go
+2. Update Date and Commit in README.chromium:
+  go run update_readme.go
diff --git a/third_party/libgav1/generate_libgav1_src_gni.go b/third_party/libgav1/generate_libgav1_src_gni.go
deleted file mode 100644
index 1bdc5ff..0000000
--- a/third_party/libgav1/generate_libgav1_src_gni.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// How to run.
-// `go run generate_libgav1_src_gni.go.` at //third_party/libgav1.
-// libgav1_src.gni is generated.
-package main
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"sort"
-	"strings"
-)
-
-const (
-	gniFile      = "libgav1_srcs.gni"
-	commonPrefix = "//third_party/libgav1/"
-	srcDir       = "./src/src"
-	header       = `# This file is generated. Do not edit.`
-)
-
-func getCppFiles(dir string) []string {
-	files, err := ioutil.ReadDir(dir)
-	if err != nil {
-		panic(err)
-	}
-
-	var paths []string
-	for _, file := range files {
-		if file.IsDir() {
-			paths = append(paths, getCppFiles(filepath.Join(dir, file.Name()))...)
-			continue
-		}
-		ext := filepath.Ext(file.Name())
-		if ext == ".cc" || ext == ".h" {
-			matches := []string{"*_test.*", "*_test_data.*"}
-			var isTestFile bool
-			for i := range matches {
-				isTestFile, err = filepath.Match(matches[i], file.Name())
-				if err != nil {
-					panic(err)
-				}
-				if isTestFile {
-					break
-				}
-			}
-			if !isTestFile {
-				paths = append(paths, filepath.Join(dir, file.Name()))
-			}
-		}
-	}
-	return paths
-}
-
-func getTopDirs(dir string) []string {
-	files, _ := ioutil.ReadDir(dir)
-	var paths []string
-	for _, file := range files {
-		if file.IsDir() {
-			paths = append(paths, filepath.Join(dir, file.Name()))
-		}
-	}
-	return paths
-}
-
-func format(dir string, files []string, file *os.File) {
-	sourcesName := "gav1_" + dir + "_sources"
-	fmt.Fprintf(file, "\n%s = [\n", sourcesName)
-	for _, f := range files {
-		fmt.Fprintf(file, "  \"%s%s\",\n", commonPrefix, f)
-	}
-	fmt.Fprintf(file, "]\n")
-}
-
-func updateReadme() {
-	gitCmd := exec.Command("bash", "-c", "git --no-pager log -1 --format=\"%cd%n%H\" --date=format:\"%A %B %d %Y\"")
-	gitCmd.Dir = "src"
-	out, err := gitCmd.Output()
-	if err != nil {
-		panic(fmt.Sprintf("failed to execute git command: %v", err))
-	}
-
-	vals := strings.Split(string(out), "\n")
-
-	if len(vals) < 2 {
-		panic(fmt.Sprintf("unexpected git log result: %v %v", vals))
-	}
-	date := vals[0]
-	hash := vals[1]
-
-	sedCmd := exec.Command("sed", "-E", "-i.back", "-e",
-		fmt.Sprintf("s/^(Date:)[[:space:]]+.*$/\\1 %s/", date), "-e",
-		fmt.Sprintf("s/^(Commit:)[[:space:]]+[a-f0-9]{40}/\\1 %s/", hash),
-		"README.chromium")
-	if err := sedCmd.Run(); err != nil {
-		panic(fmt.Sprintf("failed to execute sed command: %v %v", sedCmd, err))
-	}
-
-	rmCmd := exec.Command("rm", "README.chromium.back")
-	if rmCmd.Run() != nil {
-		panic(fmt.Sprintf("failed to execute rm command: %v", err))
-	}
-}
-
-func main() {
-	files := getCppFiles(srcDir)
-	topDirs := getTopDirs(srcDir)
-	m := make(map[string][]string)
-	for _, f := range files {
-		found := false
-		for _, d := range topDirs {
-			if strings.HasPrefix(f, d) {
-				var bd string
-				for _, asm := range []string{"sse4", "avx2"} {
-					pattern := "*_" + asm + "*"
-					if match, err := filepath.Match(pattern, filepath.Base(f)); err != nil {
-						panic(err)
-					} else if match {
-						bd = filepath.Base(d) + "_" + asm
-						break
-					}
-				}
-				if bd == "" {
-					bd = filepath.Base(d)
-				}
-
-				// Split the dsp headers out to their own variables as the
-				// assembly may depend on both its headers and the top-level
-				// headers.
-				if strings.HasPrefix(bd, "dsp") && filepath.Ext(f) == ".h" {
-					m[bd+"_headers"] = append(m[bd+"_headers"], f)
-				} else {
-					m[bd] = append(m[bd], f)
-				}
-				found = true
-				break
-			}
-		}
-		if !found {
-			m["common"] = append(m["common"], f)
-		}
-	}
-
-	if err := os.RemoveAll(gniFile); err != nil {
-		panic(err)
-	}
-
-	file, err := os.OpenFile(gniFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		panic(err)
-	}
-	fmt.Fprintf(file, "%s\n", header)
-
-	var keys []string
-	for k := range m {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
-
-	for _, k := range keys {
-		v := m[k]
-		format(k, v, file)
-	}
-	file.Close()
-
-	gnCmd := exec.Command("gn", "format", gniFile)
-	if gnCmd.Run() != nil {
-		panic(fmt.Sprintf("failed to execute gn format command: %v", err))
-	}
-
-	updateReadme()
-}
diff --git a/third_party/libgav1/libgav1_srcs.gni b/third_party/libgav1/libgav1_srcs.gni
deleted file mode 100644
index c559a1fe..0000000
--- a/third_party/libgav1/libgav1_srcs.gni
+++ /dev/null
@@ -1,277 +0,0 @@
-# This file is generated. Do not edit.
-
-gav1_common_sources = [
-  "//third_party/libgav1/src/src/buffer_pool.cc",
-  "//third_party/libgav1/src/src/buffer_pool.h",
-  "//third_party/libgav1/src/src/decoder.cc",
-  "//third_party/libgav1/src/src/decoder_impl.cc",
-  "//third_party/libgav1/src/src/decoder_impl.h",
-  "//third_party/libgav1/src/src/decoder_settings.cc",
-  "//third_party/libgav1/src/src/decoder_state.h",
-  "//third_party/libgav1/src/src/film_grain.cc",
-  "//third_party/libgav1/src/src/film_grain.h",
-  "//third_party/libgav1/src/src/frame_buffer.cc",
-  "//third_party/libgav1/src/src/frame_buffer_utils.h",
-  "//third_party/libgav1/src/src/frame_scratch_buffer.h",
-  "//third_party/libgav1/src/src/internal_frame_buffer_list.cc",
-  "//third_party/libgav1/src/src/internal_frame_buffer_list.h",
-  "//third_party/libgav1/src/src/loop_restoration_info.cc",
-  "//third_party/libgav1/src/src/loop_restoration_info.h",
-  "//third_party/libgav1/src/src/motion_vector.cc",
-  "//third_party/libgav1/src/src/motion_vector.h",
-  "//third_party/libgav1/src/src/obu_parser.cc",
-  "//third_party/libgav1/src/src/obu_parser.h",
-  "//third_party/libgav1/src/src/prediction_mask.cc",
-  "//third_party/libgav1/src/src/prediction_mask.h",
-  "//third_party/libgav1/src/src/quantizer.cc",
-  "//third_party/libgav1/src/src/quantizer.h",
-  "//third_party/libgav1/src/src/reconstruction.cc",
-  "//third_party/libgav1/src/src/reconstruction.h",
-  "//third_party/libgav1/src/src/residual_buffer_pool.cc",
-  "//third_party/libgav1/src/src/residual_buffer_pool.h",
-  "//third_party/libgav1/src/src/status_code.cc",
-  "//third_party/libgav1/src/src/symbol_decoder_context.cc",
-  "//third_party/libgav1/src/src/symbol_decoder_context.h",
-  "//third_party/libgav1/src/src/threading_strategy.cc",
-  "//third_party/libgav1/src/src/threading_strategy.h",
-  "//third_party/libgav1/src/src/version.cc",
-  "//third_party/libgav1/src/src/warp_prediction.cc",
-  "//third_party/libgav1/src/src/warp_prediction.h",
-  "//third_party/libgav1/src/src/yuv_buffer.cc",
-  "//third_party/libgav1/src/src/yuv_buffer.h",
-]
-
-gav1_dsp_sources = [
-  "//third_party/libgav1/src/src/dsp/arm/average_blend_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/cdef_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/convolve_10bit_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/convolve_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/distance_weighted_blend_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/film_grain_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intra_edge_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_cfl_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_directional_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_filter_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_smooth_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/inverse_transform_10bit_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/inverse_transform_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/loop_filter_10bit_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/loop_filter_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/loop_restoration_10bit_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/loop_restoration_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/mask_blend_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/motion_field_projection_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/motion_vector_search_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/obmc_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/super_res_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/warp_neon.cc",
-  "//third_party/libgav1/src/src/dsp/arm/weight_mask_neon.cc",
-  "//third_party/libgav1/src/src/dsp/average_blend.cc",
-  "//third_party/libgav1/src/src/dsp/cdef.cc",
-  "//third_party/libgav1/src/src/dsp/constants.cc",
-  "//third_party/libgav1/src/src/dsp/convolve.cc",
-  "//third_party/libgav1/src/src/dsp/distance_weighted_blend.cc",
-  "//third_party/libgav1/src/src/dsp/dsp.cc",
-  "//third_party/libgav1/src/src/dsp/film_grain.cc",
-  "//third_party/libgav1/src/src/dsp/intra_edge.cc",
-  "//third_party/libgav1/src/src/dsp/intrapred.cc",
-  "//third_party/libgav1/src/src/dsp/intrapred_cfl.cc",
-  "//third_party/libgav1/src/src/dsp/intrapred_directional.cc",
-  "//third_party/libgav1/src/src/dsp/intrapred_filter.cc",
-  "//third_party/libgav1/src/src/dsp/intrapred_smooth.cc",
-  "//third_party/libgav1/src/src/dsp/inverse_transform.cc",
-  "//third_party/libgav1/src/src/dsp/loop_filter.cc",
-  "//third_party/libgav1/src/src/dsp/loop_restoration.cc",
-  "//third_party/libgav1/src/src/dsp/mask_blend.cc",
-  "//third_party/libgav1/src/src/dsp/motion_field_projection.cc",
-  "//third_party/libgav1/src/src/dsp/motion_vector_search.cc",
-  "//third_party/libgav1/src/src/dsp/obmc.cc",
-  "//third_party/libgav1/src/src/dsp/super_res.cc",
-  "//third_party/libgav1/src/src/dsp/warp.cc",
-  "//third_party/libgav1/src/src/dsp/weight_mask.cc",
-]
-
-gav1_dsp_avx2_sources = [
-  "//third_party/libgav1/src/src/dsp/x86/cdef_avx2.cc",
-  "//third_party/libgav1/src/src/dsp/x86/convolve_avx2.cc",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_10bit_avx2.cc",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_avx2.cc",
-]
-
-gav1_dsp_avx2_headers_sources = [
-  "//third_party/libgav1/src/src/dsp/x86/cdef_avx2.h",
-  "//third_party/libgav1/src/src/dsp/x86/common_avx2.h",
-  "//third_party/libgav1/src/src/dsp/x86/convolve_avx2.h",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_avx2.h",
-]
-
-gav1_dsp_headers_sources = [
-  "//third_party/libgav1/src/src/dsp/arm/average_blend_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/cdef_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/common_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/convolve_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/distance_weighted_blend_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/film_grain_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intra_edge_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_cfl_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_directional_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_filter_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/intrapred_smooth_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/inverse_transform_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/loop_filter_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/loop_restoration_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/mask_blend_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/motion_field_projection_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/motion_vector_search_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/obmc_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/super_res_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/warp_neon.h",
-  "//third_party/libgav1/src/src/dsp/arm/weight_mask_neon.h",
-  "//third_party/libgav1/src/src/dsp/average_blend.h",
-  "//third_party/libgav1/src/src/dsp/cdef.h",
-  "//third_party/libgav1/src/src/dsp/common.h",
-  "//third_party/libgav1/src/src/dsp/constants.h",
-  "//third_party/libgav1/src/src/dsp/convolve.h",
-  "//third_party/libgav1/src/src/dsp/distance_weighted_blend.h",
-  "//third_party/libgav1/src/src/dsp/dsp.h",
-  "//third_party/libgav1/src/src/dsp/film_grain.h",
-  "//third_party/libgav1/src/src/dsp/film_grain_common.h",
-  "//third_party/libgav1/src/src/dsp/intra_edge.h",
-  "//third_party/libgav1/src/src/dsp/intrapred.h",
-  "//third_party/libgav1/src/src/dsp/intrapred_cfl.h",
-  "//third_party/libgav1/src/src/dsp/intrapred_directional.h",
-  "//third_party/libgav1/src/src/dsp/intrapred_filter.h",
-  "//third_party/libgav1/src/src/dsp/intrapred_smooth.h",
-  "//third_party/libgav1/src/src/dsp/inverse_transform.h",
-  "//third_party/libgav1/src/src/dsp/loop_filter.h",
-  "//third_party/libgav1/src/src/dsp/loop_restoration.h",
-  "//third_party/libgav1/src/src/dsp/mask_blend.h",
-  "//third_party/libgav1/src/src/dsp/motion_field_projection.h",
-  "//third_party/libgav1/src/src/dsp/motion_vector_search.h",
-  "//third_party/libgav1/src/src/dsp/obmc.h",
-  "//third_party/libgav1/src/src/dsp/super_res.h",
-  "//third_party/libgav1/src/src/dsp/warp.h",
-  "//third_party/libgav1/src/src/dsp/weight_mask.h",
-]
-
-gav1_dsp_sse4_sources = [
-  "//third_party/libgav1/src/src/dsp/x86/average_blend_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/cdef_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/convolve_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/distance_weighted_blend_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/film_grain_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intra_edge_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_cfl_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_directional_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_filter_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_smooth_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/inverse_transform_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/loop_filter_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_10bit_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/mask_blend_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/motion_field_projection_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/motion_vector_search_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/obmc_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/super_res_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/warp_sse4.cc",
-  "//third_party/libgav1/src/src/dsp/x86/weight_mask_sse4.cc",
-]
-
-gav1_dsp_sse4_headers_sources = [
-  "//third_party/libgav1/src/src/dsp/x86/average_blend_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/cdef_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/common_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/convolve_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/distance_weighted_blend_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/film_grain_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intra_edge_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_cfl_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_directional_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_filter_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_smooth_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/intrapred_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/inverse_transform_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/loop_filter_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/mask_blend_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/motion_field_projection_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/motion_vector_search_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/obmc_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/super_res_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/transpose_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/warp_sse4.h",
-  "//third_party/libgav1/src/src/dsp/x86/weight_mask_sse4.h",
-]
-
-gav1_gav1_sources = [
-  "//third_party/libgav1/src/src/gav1/decoder.h",
-  "//third_party/libgav1/src/src/gav1/decoder_buffer.h",
-  "//third_party/libgav1/src/src/gav1/decoder_settings.h",
-  "//third_party/libgav1/src/src/gav1/frame_buffer.h",
-  "//third_party/libgav1/src/src/gav1/status_code.h",
-  "//third_party/libgav1/src/src/gav1/symbol_visibility.h",
-  "//third_party/libgav1/src/src/gav1/version.h",
-]
-
-gav1_post_filter_sources = [
-  "//third_party/libgav1/src/src/post_filter.h",
-  "//third_party/libgav1/src/src/post_filter/cdef.cc",
-  "//third_party/libgav1/src/src/post_filter/deblock.cc",
-  "//third_party/libgav1/src/src/post_filter/loop_restoration.cc",
-  "//third_party/libgav1/src/src/post_filter/post_filter.cc",
-  "//third_party/libgav1/src/src/post_filter/super_res.cc",
-]
-
-gav1_tile_sources = [
-  "//third_party/libgav1/src/src/tile.h",
-  "//third_party/libgav1/src/src/tile/bitstream/mode_info.cc",
-  "//third_party/libgav1/src/src/tile/bitstream/palette.cc",
-  "//third_party/libgav1/src/src/tile/bitstream/partition.cc",
-  "//third_party/libgav1/src/src/tile/bitstream/transform_size.cc",
-  "//third_party/libgav1/src/src/tile/prediction.cc",
-  "//third_party/libgav1/src/src/tile/tile.cc",
-  "//third_party/libgav1/src/src/tile_scratch_buffer.cc",
-  "//third_party/libgav1/src/src/tile_scratch_buffer.h",
-]
-
-gav1_utils_sources = [
-  "//third_party/libgav1/src/src/utils/array_2d.h",
-  "//third_party/libgav1/src/src/utils/bit_mask_set.h",
-  "//third_party/libgav1/src/src/utils/bit_reader.cc",
-  "//third_party/libgav1/src/src/utils/bit_reader.h",
-  "//third_party/libgav1/src/src/utils/block_parameters_holder.cc",
-  "//third_party/libgav1/src/src/utils/block_parameters_holder.h",
-  "//third_party/libgav1/src/src/utils/blocking_counter.h",
-  "//third_party/libgav1/src/src/utils/common.h",
-  "//third_party/libgav1/src/src/utils/compiler_attributes.h",
-  "//third_party/libgav1/src/src/utils/constants.cc",
-  "//third_party/libgav1/src/src/utils/constants.h",
-  "//third_party/libgav1/src/src/utils/cpu.cc",
-  "//third_party/libgav1/src/src/utils/cpu.h",
-  "//third_party/libgav1/src/src/utils/dynamic_buffer.h",
-  "//third_party/libgav1/src/src/utils/entropy_decoder.cc",
-  "//third_party/libgav1/src/src/utils/entropy_decoder.h",
-  "//third_party/libgav1/src/src/utils/executor.cc",
-  "//third_party/libgav1/src/src/utils/executor.h",
-  "//third_party/libgav1/src/src/utils/logging.cc",
-  "//third_party/libgav1/src/src/utils/logging.h",
-  "//third_party/libgav1/src/src/utils/memory.h",
-  "//third_party/libgav1/src/src/utils/queue.h",
-  "//third_party/libgav1/src/src/utils/raw_bit_reader.cc",
-  "//third_party/libgav1/src/src/utils/raw_bit_reader.h",
-  "//third_party/libgav1/src/src/utils/reference_info.h",
-  "//third_party/libgav1/src/src/utils/segmentation.cc",
-  "//third_party/libgav1/src/src/utils/segmentation.h",
-  "//third_party/libgav1/src/src/utils/segmentation_map.cc",
-  "//third_party/libgav1/src/src/utils/segmentation_map.h",
-  "//third_party/libgav1/src/src/utils/stack.h",
-  "//third_party/libgav1/src/src/utils/threadpool.cc",
-  "//third_party/libgav1/src/src/utils/threadpool.h",
-  "//third_party/libgav1/src/src/utils/types.h",
-  "//third_party/libgav1/src/src/utils/unbounded_queue.h",
-  "//third_party/libgav1/src/src/utils/vector.h",
-]
diff --git a/third_party/libgav1/update_readme.go b/third_party/libgav1/update_readme.go
new file mode 100644
index 0000000..223f258
--- /dev/null
+++ b/third_party/libgav1/update_readme.go
@@ -0,0 +1,48 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// How to run.
+// `go run update_readme.go.` at //third_party/libgav1.
+// README.chromium is updated with the correct info.
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"strings"
+)
+
+func updateReadme() {
+	gitCmd := exec.Command("bash", "-c", "git --no-pager log -1 --format=\"%cd%n%H\" --date=format:\"%A %B %d %Y\"")
+	gitCmd.Dir = "src"
+	out, err := gitCmd.Output()
+	if err != nil {
+		panic(fmt.Sprintf("failed to execute git command: %v", err))
+	}
+
+	vals := strings.Split(string(out), "\n")
+
+	if len(vals) < 2 {
+		panic(fmt.Sprintf("unexpected git log result: %v %v", vals))
+	}
+	date := vals[0]
+	hash := vals[1]
+
+	sedCmd := exec.Command("sed", "-E", "-i.back", "-e",
+		fmt.Sprintf("s/^(Date:)[[:space:]]+.*$/\\1 %s/", date), "-e",
+		fmt.Sprintf("s/^(Commit:)[[:space:]]+[a-f0-9]{40}/\\1 %s/", hash),
+		"README.chromium")
+	if err := sedCmd.Run(); err != nil {
+		panic(fmt.Sprintf("failed to execute sed command: %v %v", sedCmd, err))
+	}
+
+	rmCmd := exec.Command("rm", "README.chromium.back")
+	if rmCmd.Run() != nil {
+		panic(fmt.Sprintf("failed to execute rm command: %v", err))
+	}
+}
+
+func main() {
+	updateReadme()
+}
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index b110f90..a991d4b 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -35,8 +35,8 @@
 # https://chromium.googlesource.com/chromium/src/+/main/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
 # This is the output of `git describe` and is usable as a commit-ish.
-CLANG_REVISION = 'llvmorg-16-init-10467-g1239d37b'
-CLANG_SUB_REVISION = 2
+CLANG_REVISION = 'llvmorg-16-init-10736-ged9638c4'
+CLANG_SUB_REVISION = 1
 
 PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 RELEASE_VERSION = '16'
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 851f810..ee40f75 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -363,7 +363,6 @@
       'Linux Builder (reclient compare)': 'gpu_tests_release_bot_reclient',
       'Linux CFI (reclient shadow)': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_reclient',
       'Linux ChromiumOS MSan Focal': 'chromeos_msan_focal_release_bot_reclient',
-      'Linux MSan Focal': 'msan_focal_release_bot_reclient',
       'Linux Viz': 'release_trybot_minimal_symbols_reclient',
       # TODO(crbug.com/1260232): remove this after the migration.
       'Mac Builder (reclient compare)': 'gpu_tests_release_bot_minimal_symbols_reclient',
@@ -434,7 +433,6 @@
       'linux-lacros-dbg-fyi': 'lacros_on_linux_debug_bot_reclient',
       'linux-lacros-dbg-tests-fyi': 'lacros_on_linux_debug_bot',
       'linux-lacros-tester-fyi-rel': 'lacros_on_linux_release_bot',
-      'linux-lacros-tester-rel-reviver': 'lacros_on_linux_release_bot_reclient',
       'linux-lacros-version-skew-fyi': 'lacros_on_linux_release_not_build_ash_bot_reclient',
       'linux-perfetto-rel': 'perfetto_release_bot_reclient',
       'linux-rel-no-external-ip': 'gpu_tests_release_bot_do_typecheck_reclient',
@@ -621,7 +619,7 @@
       'Linux CFI': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_reclient',
       'Linux Chromium OS ASan LSan Builder': 'asan_lsan_chromeos_release_bot_dcheck_always_on_reclient',
       'Linux ChromiumOS MSan Builder': 'chromeos_msan_release_bot_reclient',
-      'Linux MSan Builder': 'msan_release_bot_reclient',
+      'Linux MSan Builder': 'msan_focal_release_bot_reclient',
       'Linux TSan Builder': 'tsan_disable_nacl_release_bot_reclient',
       'Mac ASan 64 Builder': 'asan_minimal_symbols_disable_nacl_release_bot_dcheck_always_on_reclient',
       'WebKit Linux ASAN': 'asan_lsan_release_bot_blink_reclient',
@@ -911,6 +909,7 @@
       'linux-chrome-beta': 'official_goma',
       'linux-chrome-stable': 'official_goma',
       'linux-chromeos-chrome': 'official_goma_chromeos_include_unwind_tables',
+      'linux-chromeos-compile-chrome': 'official_goma_chromeos_include_unwind_tables',
       'linux-finch-smoke-chrome': 'official_goma',
       'mac-arm64-finch-smoke-chrome': 'official_goma_mac_arm',
       'mac-chrome': 'official_goma_mac',
@@ -938,7 +937,7 @@
 
     'tryserver.chromium': {
       'android-official': 'android_official_optimize_reclient_trybot',
-      'fuchsia-official': 'fuchsia_official_optimize_goma_trybot',
+      'fuchsia-official': 'fuchsia_official_optimize_reclient_trybot',
       'linux-official': 'official_optimize_reclient_trybot',
       'mac-official': 'official_optimize_goma_trybot',
       'win-official': 'official_optimize_reclient_trybot',
@@ -1069,7 +1068,6 @@
       'linux-lacros-dbg': 'lacros_on_linux_debug_bot',
       'linux-lacros-rel': 'lacros_on_linux_release_trybot_reclient',
       'linux-lacros-rel-code-coverage': 'lacros_on_linux_release_trybot_coverage',
-      'linux-lacros-tester-rel-reviver': 'lacros_on_linux_release_trybot',
     },
 
     'tryserver.chromium.codesearch': {
@@ -1094,22 +1092,22 @@
       'dawn-try-win10-x86-rel': 'dawn_tests_release_trybot_x86',
       'dawn-win10-x64-deps-rel': 'dawn_tests_release_trybot',
       'dawn-win10-x86-deps-rel': 'dawn_tests_release_trybot_x86_reclient',
-      'linux-dawn-rel': 'dawn_tests_with_desktop_gl_release_trybot',
+      'linux-dawn-rel': 'dawn_tests_with_desktop_gl_release_trybot_reclient',
       'mac-dawn-rel': 'dawn_tests_release_trybot_alloc_none',
       'win-dawn-rel': 'dawn_tests_release_trybot_reclient',
     },
 
     'tryserver.chromium.fuchsia': {
-      'fuchsia-arm64-cast-receiver-rel': 'release_trybot_fuchsia_arm64_cast_receiver',
+      'fuchsia-arm64-cast-receiver-rel': 'release_trybot_fuchsia_arm64_cast_receiver_reclient',
       'fuchsia-arm64-chrome-rel': 'release_trybot_fuchsia_arm64',
       'fuchsia-arm64-rel': 'release_trybot_fuchsia_arm64',
       'fuchsia-arm64-rel-orchestrator': 'release_trybot_fuchsia_arm64',
-      'fuchsia-binary-size': 'release_fuchsia_arm64_binary_size',
-      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
-      'fuchsia-deterministic-dbg': 'debug_bot_fuchsia',
-      'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64',
-      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
-      'fuchsia-x64-cast-receiver-rel': 'release_trybot_fuchsia_cast_receiver',
+      'fuchsia-binary-size': 'release_fuchsia_arm64_binary_size_reclient',
+      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only_reclient',
+      'fuchsia-deterministic-dbg': 'debug_bot_fuchsia_reclient',
+      'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64_reclient',
+      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia_reclient',
+      'fuchsia-x64-cast-receiver-rel': 'release_trybot_fuchsia_cast_receiver_reclient',
       'fuchsia-x64-chrome-rel': 'release_trybot_fuchsia_chrome',
       'fuchsia-x64-rel': 'release_trybot_fuchsia',
       'fuchsia-x64-workstation': 'release_trybot_fuchsia_chrome',
@@ -1182,8 +1180,7 @@
       # This is intentionally a release_bot and not a release_trybot;
       # enabling DCHECKs seems to cause flaky failures that don't show up
       # on the continuous builder.
-      'linux_chromium_msan_focal': 'msan_focal_release_bot_reclient',
-      'linux_chromium_msan_rel_ng': 'msan_release_bot_reclient',
+      'linux_chromium_msan_rel_ng': 'msan_focal_release_bot_reclient',
 
       'linux_chromium_tsan_rel_ng': 'tsan_disable_nacl_release_trybot_reclient',
 
@@ -1297,7 +1294,7 @@
 
     'tryserver.chromium.tricium': {
       'android-clang-tidy-rel': 'android_release_trybot_reclient',
-      'fuchsia-clang-tidy-rel': 'release_trybot_fuchsia',
+      'fuchsia-clang-tidy-rel': 'release_trybot_fuchsia_reclient',
       'ios-clang-tidy-rel': 'ios_device_release_compile_only',
       'linux-chromeos-clang-tidy-rel': 'chromeos_with_codecs_release_trybot',
       'linux-clang-tidy-dbg': 'debug_bot_reclient',
@@ -2470,10 +2467,6 @@
       'dawn_tests', 'release_trybot_reclient', 'x86',
     ],
 
-    'dawn_tests_with_desktop_gl_release_trybot': [
-      'dawn_tests', 'dawn_enable_desktop_gl', 'release_trybot_minimal_symbols',
-    ],
-
     'dawn_tests_with_desktop_gl_release_trybot_reclient': [
       'dawn_tests', 'dawn_enable_desktop_gl', 'release_trybot_minimal_symbols_reclient',
     ],
@@ -2494,22 +2487,10 @@
       'debug_bot_reclient', 'enable_blink_animation_use_time_delta',
     ],
 
-    'debug_bot_fuchsia': [
-      'debug_bot', 'fuchsia',
-    ],
-
-    'debug_bot_fuchsia_arm64': [
-      'debug_bot', 'fuchsia', 'arm64', 'arm64_host',
-    ],
-
     'debug_bot_fuchsia_arm64_reclient': [
       'debug_bot_reclient', 'fuchsia', 'arm64', 'arm64_host',
     ],
 
-    'debug_bot_fuchsia_compile_only': [
-      'debug_bot', 'fuchsia', 'compile_only',
-    ],
-
     'debug_bot_fuchsia_compile_only_reclient': [
       'debug_bot_reclient', 'fuchsia', 'compile_only',
     ],
@@ -2556,14 +2537,14 @@
       'fuchsia', 'release', 'clang_tot', 'static',
     ],
 
-    'fuchsia_official_optimize_goma_trybot': [
-      'official_optimize_goma_trybot', 'fuchsia',
-    ],
-
     'fuchsia_official_optimize_reclient': [
       'official_optimize_reclient', 'fuchsia',
     ],
 
+    'fuchsia_official_optimize_reclient_trybot': [
+      'official_optimize_reclient_trybot', 'fuchsia',
+    ],
+
     'gn_linux_upload': [
       'gn_linux_upload', 'official', 'goma',
     ],
@@ -3571,8 +3552,8 @@
       'release_bot_reclient', 'x86', 'minimal_symbols',
     ],
 
-    'release_fuchsia_arm64_binary_size': [
-      'release', 'official_optimize_goma', 'fuchsia', 'arm64', 'cast_receiver_size_optimized',
+    'release_fuchsia_arm64_binary_size_reclient': [
+      'release', 'official_optimize_reclient', 'fuchsia', 'arm64', 'cast_receiver_size_optimized',
     ],
 
     'release_rust_android_arm_reclient': [
@@ -3635,18 +3616,22 @@
       'release_trybot', 'fuchsia', 'arm64', 'arm64_host',
     ],
 
-    'release_trybot_fuchsia_arm64_cast_receiver': [
-      'release_trybot', 'fuchsia', 'arm64', 'arm64_host', 'cast_receiver_size_optimized',
+    'release_trybot_fuchsia_arm64_cast_receiver_reclient': [
+      'release_trybot_reclient', 'fuchsia', 'arm64', 'arm64_host', 'cast_receiver_size_optimized',
     ],
 
-    'release_trybot_fuchsia_cast_receiver': [
-      'release_trybot', 'fuchsia', 'cast_receiver_size_optimized',
+    'release_trybot_fuchsia_cast_receiver_reclient': [
+      'release_trybot_reclient', 'fuchsia', 'cast_receiver_size_optimized',
     ],
 
     'release_trybot_fuchsia_chrome': [
       'release_trybot', 'fuchsia', 'fuchsia_chrome'
     ],
 
+    'release_trybot_fuchsia_reclient': [
+      'release_trybot_reclient', 'fuchsia',
+    ],
+
     'release_trybot_minimal_symbols_reclient': [
       'release_trybot_minimal_symbols_reclient',
     ],
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index 2fdc89db..7314e6f 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -438,17 +438,6 @@
       "use_remoteexec": true
     }
   },
-  "Linux MSan Focal": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "instrumented_libraries_release": "focal",
-      "is_component_build": false,
-      "is_debug": false,
-      "is_msan": true,
-      "msan_track_origins": 2,
-      "use_remoteexec": true
-    }
-  },
   "Linux Viz": {
     "gn_args": {
       "dcheck_always_on": true,
@@ -1278,17 +1267,6 @@
       "use_goma": true
     }
   },
-  "linux-lacros-tester-rel-reviver": {
-    "gn_args": {
-      "also_build_ash_chrome": true,
-      "chromeos_is_browser_only": true,
-      "dcheck_always_on": false,
-      "is_component_build": false,
-      "is_debug": false,
-      "target_os": "chromeos",
-      "use_remoteexec": true
-    }
-  },
   "linux-lacros-version-skew-fyi": {
     "gn_args": {
       "chromeos_is_browser_only": true,
diff --git a/tools/mb/mb_config_expectations/chromium.memory.json b/tools/mb/mb_config_expectations/chromium.memory.json
index b8ad634..f026f05 100644
--- a/tools/mb/mb_config_expectations/chromium.memory.json
+++ b/tools/mb/mb_config_expectations/chromium.memory.json
@@ -50,7 +50,7 @@
   "Linux MSan Builder": {
     "gn_args": {
       "dcheck_always_on": false,
-      "instrumented_libraries_release": "xenial",
+      "instrumented_libraries_release": "focal",
       "is_component_build": false,
       "is_debug": false,
       "is_msan": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chrome.json b/tools/mb/mb_config_expectations/tryserver.chrome.json
index 129f641..eff0052 100644
--- a/tools/mb/mb_config_expectations/tryserver.chrome.json
+++ b/tools/mb/mb_config_expectations/tryserver.chrome.json
@@ -359,6 +359,16 @@
       "use_goma": true
     }
   },
+  "linux-chromeos-compile-chrome": {
+    "gn_args": {
+      "exclude_unwind_tables": false,
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "symbol_level": 1,
+      "target_os": "chromeos",
+      "use_goma": true
+    }
+  },
   "linux-finch-smoke-chrome": {
     "gn_args": {
       "is_chrome_branded": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
index 426c800..c83e982 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
@@ -377,17 +377,5 @@
       "use_clang_coverage": true,
       "use_goma": true
     }
-  },
-  "linux-lacros-tester-rel-reviver": {
-    "gn_args": {
-      "also_build_ash_chrome": true,
-      "chromeos_is_browser_only": true,
-      "dcheck_always_on": true,
-      "is_component_build": false,
-      "is_debug": false,
-      "symbol_level": 0,
-      "target_os": "chromeos",
-      "use_goma": true
-    }
   }
 }
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.dawn.json b/tools/mb/mb_config_expectations/tryserver.chromium.dawn.json
index 3db5191..4ad5ea67 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.dawn.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.dawn.json
@@ -137,7 +137,7 @@
       "is_debug": false,
       "symbol_level": 1,
       "use_dawn": true,
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "mac-dawn-rel": {
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.fuchsia.json b/tools/mb/mb_config_expectations/tryserver.chromium.fuchsia.json
index d16f57b5..67d4e82 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.fuchsia.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.fuchsia.json
@@ -9,7 +9,7 @@
       "target_cpu": "arm64",
       "target_os": "fuchsia",
       "test_host_cpu": "arm64",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-arm64-chrome-rel": {
@@ -56,7 +56,7 @@
       "is_official_build": true,
       "target_cpu": "arm64",
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-compile-x64-dbg": {
@@ -65,7 +65,7 @@
       "is_debug": true,
       "symbol_level": 0,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-deterministic-dbg": {
@@ -74,7 +74,7 @@
       "is_debug": true,
       "symbol_level": 1,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-fyi-arm64-dbg": {
@@ -85,7 +85,7 @@
       "target_cpu": "arm64",
       "target_os": "fuchsia",
       "test_host_cpu": "arm64",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-fyi-x64-dbg": {
@@ -94,7 +94,7 @@
       "is_debug": true,
       "symbol_level": 1,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-x64-cast-receiver-rel": {
@@ -105,7 +105,7 @@
       "is_debug": false,
       "symbol_level": 0,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "fuchsia-x64-chrome-rel": {
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.json b/tools/mb/mb_config_expectations/tryserver.chromium.json
index a0fe32aa..7822daf 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.json
@@ -15,7 +15,7 @@
       "is_official_build": true,
       "symbol_level": 1,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "linux-official": {
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index 62af472..9fbf21b0 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -682,21 +682,10 @@
       "use_remoteexec": true
     }
   },
-  "linux_chromium_msan_focal": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "instrumented_libraries_release": "focal",
-      "is_component_build": false,
-      "is_debug": false,
-      "is_msan": true,
-      "msan_track_origins": 2,
-      "use_remoteexec": true
-    }
-  },
   "linux_chromium_msan_rel_ng": {
     "gn_args": {
       "dcheck_always_on": false,
-      "instrumented_libraries_release": "xenial",
+      "instrumented_libraries_release": "focal",
       "is_component_build": false,
       "is_debug": false,
       "is_msan": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.tricium.json b/tools/mb/mb_config_expectations/tryserver.chromium.tricium.json
index 34ccec6..8fc9dfca 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.tricium.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.tricium.json
@@ -20,7 +20,7 @@
       "is_debug": false,
       "symbol_level": 0,
       "target_os": "fuchsia",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "ios-clang-tidy-rel": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b395bec..b89c2737 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -18146,11 +18146,6 @@
   <int value="5" label="Try to override the status when its valus is error."/>
 </enum>
 
-<enum name="ConcerningHeaderPresent">
-  <int value="0" label="Header not present"/>
-  <int value="1" label="Header present"/>
-</enum>
-
 <enum name="ConditionalFocusDecision">
   <int value="0" label="kExplicitFocusCapturedSurface"/>
   <int value="1" label="kExplicitNoFocusChange"/>
@@ -36682,6 +36677,7 @@
   <int value="1734" label="OS_DIAGNOSTICS_RUNNVMESELFTESTROUTINE"/>
   <int value="1735" label="AUTOTESTPRIVATE_STARTFRAMECOUNTING"/>
   <int value="1736" label="AUTOTESTPRIVATE_STOPFRAMECOUNTING"/>
+  <int value="1737" label="FEEDBACKPRIVATE_OPENFEEDBACK"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -64727,6 +64723,7 @@
       label="ExperimentalAccessibilityDictationMoreCommands:disabled"/>
   <int value="1874050287" label="UploadOfficeToCloud:enabled"/>
   <int value="1874195462" label="ChromeHomeMenuItemsExpandSheet:disabled"/>
+  <int value="1874203133" label="SystemSounds:enabled"/>
   <int value="1874604540" label="UseSuggestionsEvenIfFew:disabled"/>
   <int value="1875156497" label="CaptureMode:enabled"/>
   <int value="1877769074" label="PhoneHubFeatureSetupErrorHandling:enabled"/>
@@ -65103,6 +65100,7 @@
   <int value="2095740699" label="OmniboxPedalsBatch3:disabled"/>
   <int value="2096736155" label="BrowsingDataLifetimeManager:enabled"/>
   <int value="2097048479" label="disable-auto-hiding-toolbar-threshold"/>
+  <int value="2097313481" label="SystemSounds:disabled"/>
   <int value="2097465503" label="RequestDesktopSiteZoom:disabled"/>
   <int value="2097585272" label="ProductivityLauncherImageSearch:enabled"/>
   <int value="2098059607" label="WifiSyncAllowDeletes:disabled"/>
@@ -72850,20 +72848,6 @@
   <int value="4" label="Not responding"/>
 </enum>
 
-<enum name="NetworkServiceConcerningRequestHeaders">
-  <int value="0" label="kConnection"/>
-  <int value="1" label="kCookie"/>
-  <int value="2" label="kCookie2"/>
-  <int value="3" label="kContentTransferEncoding"/>
-  <int value="4" label="kDate"/>
-  <int value="5" label="kExpect"/>
-  <int value="6" label="kKeepAlive"/>
-  <int value="7" label="kReferer"/>
-  <int value="8" label="kTe"/>
-  <int value="9" label="kTransferEncoding"/>
-  <int value="10" label="kVia"/>
-</enum>
-
 <enum name="NetworkServiceCorpResult">
   <int value="0" label="success"/>
   <int value="1" label="same-origin violation"/>
@@ -91262,6 +91246,20 @@
              start)"/>
 </enum>
 
+<enum name="SecagentdBpfAttachResult">
+  <int value="0" label="BPF loaded and attached successfully"/>
+  <int value="1" label="BPF failed to Open"/>
+  <int value="2" label="BPF failed to Load"/>
+  <int value="3" label="BPF failed to Attach"/>
+  <int value="4" label="BPF failed Ring Buffer creation"/>
+</enum>
+
+<enum name="SecagentdPolicy">
+  <int value="0"
+      label="Device XDR reporting policy was checked at startup (baseline)"/>
+  <int value="1" label="XDR reporting was enabled by Device policy"/>
+</enum>
+
 <enum name="SecondaryAccountConsentLoggerResult">
   <obsolete>
     Deprecated in M91 after v2 EduCoexistence.
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index b4c8a4f..17bc2cb 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -903,7 +903,7 @@
 
 <histogram
     name="Android.ChildProcessBinding.Percentage{ChildProcessConnectionMetricsBindingState}Connections_{TotalConnectionBucket}"
-    units="%" expires_after="2023-02-12">
+    units="%" expires_after="2023-04-16">
   <owner>ckitagawa@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -940,7 +940,7 @@
 
 <histogram
     name="Android.ChildProcessBinding.{ChildProcessConnectionMetricsBindingState}Connections"
-    units="connections" expires_after="2022-12-01">
+    units="connections" expires_after="2023-04-16">
   <owner>ckitagawa@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index c5cb0ae..05565f65 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -3451,6 +3451,29 @@
   <summary>Usage of the &quot;Scan card&quot; control item.</summary>
 </histogram>
 
+<histogram name="Autofill.Sectioning.FieldsPerSection" units="Fields"
+    expires_after="2023-03-31">
+  <owner>fleimgruber@google.com</owner>
+  <owner>schwering@google.com</owner>
+  <owner>chrome-autofill-alerts@google.com</owner>
+  <summary>
+    The number of fields per section after sectioning a form using
+    kAutofillUseParameterizedSectioning. Emitted once per section every time
+    sectioning is done.
+  </summary>
+</histogram>
+
+<histogram name="Autofill.Sectioning.NumberOfSections" units="Sections"
+    expires_after="2023-03-31">
+  <owner>fleimgruber@google.com</owner>
+  <owner>schwering@google.com</owner>
+  <owner>chrome-autofill-alerts@google.com</owner>
+  <summary>
+    The number of sections after sectioning a form using
+    kAutofillUseParameterizedSectioning. Emitted every time sectioning is done.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.ServerCardLinkClicked" enum="AutofillSyncState"
     expires_after="2023-09-30">
   <owner>jsaul@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 59420a14..3059a74 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1811,6 +1811,35 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Secagentd.Bpf.{Bpf}.AttachResult"
+    enum="SecagentdBpfAttachResult" expires_after="2023-05-18">
+  <owner>aashay@google.com</owner>
+  <owner>cros-enterprise-security@google.com</owner>
+  <summary>
+    Records the initialization status of the {Bpf} BPF used by the CrOS
+    secagentd daemon. Emits either a success or a specific error value.
+  </summary>
+  <token key="Bpf">
+    <variant name="Process" summary="Process BPF"/>
+  </token>
+</histogram>
+
+<histogram name="ChromeOS.Secagentd.Policy" enum="SecagentdPolicy"
+    expires_after="2023-05-18">
+  <owner>aashay@google.com</owner>
+  <owner>cros-enterprise-security@google.com</owner>
+  <summary>
+    Records whether the XDR event reporting policy was found enabled for a
+    device. Also provides an on-check enum for baseline. All of the
+    functionality of the CrOS secagentd daemon is gated by this enterprise
+    policy.
+
+    Note that even though the daemon regularly polls device policy for updates,
+    it will emit exactly one on-check value and at most one enabled value during
+    its lifetime (generally per device per boot).
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.SecurityAnomaly" enum="SecurityAnomaly"
     expires_after="2023-04-16">
   <owner>jorgelo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index e3dd5f87..4b9759b 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -510,7 +510,7 @@
 </histogram>
 
 <histogram name="Cookie.IncludedRequestEffectiveSameSite"
-    enum="CookieEffectiveSameSite" expires_after="2023-01-01">
+    enum="CookieEffectiveSameSite" expires_after="2023-07-01">
   <owner>bingler@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index fab4852..721f586 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -5852,44 +5852,6 @@
   <affected-histogram name="ServiceWorker.StartWorker.Time"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="ServiceWorkerScheduler" separator="."
-    ordering="prefix">
-  <suffix name="BackgroundSyncManager"
-      label="Collected from a BackgroundSyncManager instance"/>
-  <suffix name="Cache" label="Collected from a CacheStorageCache instance"/>
-  <suffix name="CacheStorage" label="Collected from a CacheStorage instance"/>
-  <affected-histogram name="ServiceWorkerCache.Scheduler.OperationDuration2"/>
-  <affected-histogram name="ServiceWorkerCache.Scheduler.QueueDuration2"/>
-  <affected-histogram name="ServiceWorkerCache.Scheduler.QueueLength"/>
-</histogram_suffixes>
-
-<histogram_suffixes name="ServiceWorkerSchedulerOp" separator=".">
-  <suffix name="Close" label=""/>
-  <suffix name="Delete" label=""/>
-  <suffix name="GetAllMatched" label=""/>
-  <suffix name="Has" label=""/>
-  <suffix name="Init" label=""/>
-  <suffix name="Keys" label=""/>
-  <suffix name="Match" label=""/>
-  <suffix name="MatchAll" label=""/>
-  <suffix name="Open" label=""/>
-  <suffix name="Put" label=""/>
-  <suffix name="Size" label=""/>
-  <suffix name="SizeThenClose" label=""/>
-  <suffix name="WriteIndex" label=""/>
-  <suffix name="WriteSideData" label=""/>
-  <affected-histogram
-      name="ServiceWorkerCache.Cache.Scheduler.OperationDuration2"/>
-  <affected-histogram name="ServiceWorkerCache.Cache.Scheduler.QueueDuration2"/>
-  <affected-histogram name="ServiceWorkerCache.Cache.Scheduler.QueueLength"/>
-  <affected-histogram
-      name="ServiceWorkerCache.CacheStorage.Scheduler.OperationDuration2"/>
-  <affected-histogram
-      name="ServiceWorkerCache.CacheStorage.Scheduler.QueueDuration2"/>
-  <affected-histogram
-      name="ServiceWorkerCache.CacheStorage.Scheduler.QueueLength"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="SessionRestoreTabCounts" separator="_">
   <suffix name="1" label="1 tab present"/>
   <suffix name="2" label="2 tabs present"/>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 0b2fe2f3..06509b8 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1077,6 +1077,39 @@
   </token>
 </histogram>
 
+<histogram name="History.Clusters.ContextClusterer.NumClusters.AtCleanUp"
+    units="num clusters" expires_after="2023-10-01">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>chrome-journeys@google.com</owner>
+  <component>UI&gt;Browser&gt;Journeys</component>
+  <summary>
+    Number of clusters that are in-progress. Recorded once an hour
+    (Finch-configurable) prior to the clean-up pass.
+  </summary>
+</histogram>
+
+<histogram name="History.Clusters.ContextClusterer.NumClusters.CleanedUp"
+    units="num clusters" expires_after="2023-10-01">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>chrome-journeys@google.com</owner>
+  <component>UI&gt;Browser&gt;Journeys</component>
+  <summary>
+    Number of clusters that were cleaned up in the clean-up pass. Recorded once
+    an hour (Finch-configurable) during the clean-up pass.
+  </summary>
+</histogram>
+
+<histogram name="History.Clusters.ContextClusterer.NumClusters.PostCleanUp"
+    units="num clusters" expires_after="2023-10-01">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>chrome-journeys@google.com</owner>
+  <component>UI&gt;Browser&gt;Journeys</component>
+  <summary>
+    Number of clusters that are in-progress. Recorded once an hour
+    (Finch-configurable) after the clean-up pass.
+  </summary>
+</histogram>
+
 <histogram name="History.Clusters.KeywordCache.Latency" units="ms"
     expires_after="2023-10-01">
   <owner>tommycli@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index fd3e75a..cf4dda04 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -2659,30 +2659,6 @@
   <token key="EmeApi" variants="EmeApi"/>
 </histogram>
 
-<histogram name="Media.Engagement.PreloadedList.CheckResult"
-    enum="PreloadedListCheckResult" expires_after="M82">
-  <owner>beccahughes@chromium.org</owner>
-  <owner>media-dev@chromium.org</owner>
-  <summary>
-    Recorded when the Media Engagement Preloaded List is checked whether a
-    string is present on that list. If the check was successful then the result
-    of the check is recorded in this histogram. If the check was not successful
-    then the reason for the check failing is recorded.
-  </summary>
-</histogram>
-
-<histogram name="Media.Engagement.PreloadedList.LoadResult"
-    enum="PreloadedListLoadResult" expires_after="M82">
-  <owner>beccahughes@chromium.org</owner>
-  <owner>media-dev@chromium.org</owner>
-  <summary>
-    Recorded when data is loaded into the Media Engagement Preloaded List. If
-    the load is successful then &quot;loaded&quot; is recorded to this
-    histogram. If the load was not successful then the reason why is recorded to
-    this histogram.
-  </summary>
-</histogram>
-
 <histogram name="Media.Engagement.ScoreAtPlayback" units="%"
     expires_after="M82">
   <owner>beccahughes@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index d150809..21baf2c 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -3239,23 +3239,6 @@
   </summary>
 </histogram>
 
-<histogram name="Network.URLLoader.BodyReadFromNetBeforePaused" units="bytes"
-    expires_after="M77">
-  <owner>yzshen@chromium.org</owner>
-  <summary>
-    How much, in bytes, of the response body has been read from network by a
-    URLLoader before it pauses reading, when it receives a
-    PauseReadingBodyFromNet() call. If there are multiple calls to
-    PauseReadingBodyFromNet(), only a single value is recorded for the last
-    call. This histogram is recorded by URLLoader implementations that fetch
-    from network. When SafeBrowsing indicates that a resource may be unsafe and
-    therefore a more time-consuming check is required to classify it, reading
-    response body from network is paused in order to reduce the chance of
-    writing unsafe contents into cache. This histogram is useful to evaluate how
-    much data is cached during this window.
-  </summary>
-</histogram>
-
 <histogram name="Network.Wifi.Channel" enum="NetworkChannelType"
     expires_after="M85">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
@@ -3402,55 +3385,6 @@
   </summary>
 </histogram>
 
-<histogram name="NetworkService.ConcerningRequestHeader.AddedOnRedirect"
-    enum="ConcerningHeaderPresent" expires_after="2019-11-01">
-  <owner>mmenke@chromium.org</owner>
-  <summary>
-    Whether a request going through the network service has one of a number of
-    concerning headers added by the caller when the request is redirected.
-    Entries are only recorded when headers are actually added during a redirect.
-    We want to figure out if we can ban any of these headers from being set
-    outside the network service. For comparison with
-    NetworkService.ConcerningRequestHeader.HeaderAddedOnRedirect.
-  </summary>
-</histogram>
-
-<histogram name="NetworkService.ConcerningRequestHeader.HeaderAddedOnRedirect"
-    enum="NetworkServiceConcerningRequestHeaders" expires_after="2019-11-01">
-  <owner>mmenke@chromium.org</owner>
-  <summary>
-    Tracks how often each of a number of concerning headers are added by the
-    caller when a network service request is redirected. We want to figure out
-    if we can ban any of these headers from being set outside the network
-    service. To get meaningful percentages of requests, need to compare with
-    NetworkService.ConcerningRequestHeader.AddedOnRedirect.
-  </summary>
-</histogram>
-
-<histogram name="NetworkService.ConcerningRequestHeader.HeaderPresentOnStart"
-    enum="NetworkServiceConcerningRequestHeaders" expires_after="2019-11-01">
-  <owner>mmenke@chromium.org</owner>
-  <summary>
-    Tracks how often each of a number of concerning headers are set by the
-    caller when a network service request is started. We want to figure out if
-    we can ban any of these headers from being set outside the network service.
-    To get meaningful percentages of requests, need to compare with
-    NetworkService.ConcerningRequestHeader.PresentOnStart.
-  </summary>
-</histogram>
-
-<histogram name="NetworkService.ConcerningRequestHeader.PresentOnStart"
-    enum="ConcerningHeaderPresent" expires_after="2019-11-01">
-  <owner>mmenke@chromium.org</owner>
-  <summary>
-    Whether a request going through the network service has one of a number of
-    concerning headers set by the caller when the request starts. We want to
-    figure out if we can ban any of these headers from being set outside the
-    network service. For comparison with
-    NetworkService.ConcerningRequestHeader.HeadersPresentOnStart.
-  </summary>
-</histogram>
-
 <histogram name="NetworkService.CorsPreflightMethodAllowed"
     enum="NetworkServiceCorsPreflightMethodAllowed" expires_after="2023-02-12">
   <owner>hiroshige@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/quota/histograms.xml b/tools/metrics/histograms/metadata/quota/histograms.xml
index 6fd458d..4716eb2 100644
--- a/tools/metrics/histograms/metadata/quota/histograms.xml
+++ b/tools/metrics/histograms/metadata/quota/histograms.xml
@@ -62,6 +62,22 @@
   </summary>
 </histogram>
 
+<histogram name="Quota.DatabaseMigration{VersionUpgrades}"
+    enum="BooleanSuccess" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    Records whether the database migration was successful. Recorded after
+    attempting to migrate the database.
+  </summary>
+  <token key="VersionUpgrades">
+    <variant name="FromV5ToV7"/>
+    <variant name="FromV6ToV7"/>
+    <variant name="FromV7ToV8"/>
+    <variant name="FromV8ToV9"/>
+  </token>
+</histogram>
+
 <histogram name="Quota.DiskspaceShortage" units="MB" expires_after="2023-04-30">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/service/histograms.xml b/tools/metrics/histograms/metadata/service/histograms.xml
index 9e14178..42fc951 100644
--- a/tools/metrics/histograms/metadata/service/histograms.xml
+++ b/tools/metrics/histograms/metadata/service/histograms.xml
@@ -22,6 +22,14 @@
 
 <histograms>
 
+<variants name="ServiceWorkerCacheClientType">
+  <variant name="BackgroundSyncManager"
+      summary="Collected from a BackgroundSyncManager instance"/>
+  <variant name="Cache" summary="Collected from a CacheStorageCache instance"/>
+  <variant name="CacheStorage"
+      summary="Collected from a CacheStorage instance"/>
+</variants>
+
 <variants name="ServiceWorkerCacheProcessType">
   <variant name="Browser"
       summary="Measurements taken in the browser process on the IO thread"/>
@@ -30,6 +38,12 @@
                thread"/>
 </variants>
 
+<variants name="ServiceWorkerCacheSchedulerOpClientType">
+  <variant name="Cache" summary="Collected from a CacheStorageCache instance"/>
+  <variant name="CacheStorage"
+      summary="Collected from a CacheStorage instance"/>
+</variants>
+
 <variants name="ServiceWorkerEventType">
   <variant name="ACTIVATE"/>
   <variant name="BACKGROUND_FETCH_ABORT"/>
@@ -58,6 +72,23 @@
   <variant name="SHOULD_FALLBACK"/>
 </variants>
 
+<variants name="ServiceWorkerSchedulerOp">
+  <variant name="Close"/>
+  <variant name="Delete"/>
+  <variant name="GetAllMatched"/>
+  <variant name="Has"/>
+  <variant name="Init"/>
+  <variant name="Keys"/>
+  <variant name="Match"/>
+  <variant name="MatchAll"/>
+  <variant name="Open"/>
+  <variant name="Put"/>
+  <variant name="Size"/>
+  <variant name="SizeThenClose"/>
+  <variant name="WriteIndex"/>
+  <variant name="WriteSideData"/>
+</variants>
+
 <histogram name="ServiceWorker.AbortPaymentEvent.Time" units="ms"
     expires_after="2023-10-18">
   <owner>rouslan@chromium.org</owner>
@@ -1419,8 +1450,9 @@
   </summary>
 </histogram>
 
-<histogram name="ServiceWorkerCache.Scheduler.OperationDuration2" units="ms"
-    expires_after="2023-05-26">
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheClientType}.Scheduler.OperationDuration2"
+    units="ms" expires_after="2023-05-26">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
@@ -1428,27 +1460,77 @@
     histogram differs from the old OperationDuration in that it uses a different
     bucket size to measure longer values.
   </summary>
+  <token key="ServiceWorkerCacheClientType"
+      variants="ServiceWorkerCacheClientType"/>
 </histogram>
 
-<histogram name="ServiceWorkerCache.Scheduler.QueueDuration2" units="ms"
-    expires_after="2023-05-26">
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheClientType}.Scheduler.QueueDuration2"
+    units="ms" expires_after="2023-05-26">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
-    The time in ms from when an operation was queued until its task is posted.
-    This histogram differs from the old QueueDuration in that it uses a
-    different bucket size to measure longer values.
+    The time in ms from when an operation is started until it completes. This
+    histogram differs from the old OperationDuration in that it uses a different
+    bucket size to measure longer values.
   </summary>
+  <token key="ServiceWorkerCacheClientType"
+      variants="ServiceWorkerCacheClientType"/>
 </histogram>
 
-<histogram name="ServiceWorkerCache.Scheduler.QueueLength" units="operations"
-    expires_after="2023-05-26">
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheClientType}.Scheduler.QueueLength"
+    units="operations" expires_after="2023-05-26">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The number of operations in the scheduling queue just before enqueuing a new
     operation.
   </summary>
+  <token key="ServiceWorkerCacheClientType"
+      variants="ServiceWorkerCacheClientType"/>
+</histogram>
+
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheSchedulerOpClientType}.Scheduler.OperationDuration2.{ServiceWorkerSchedulerOp}"
+    units="ms" expires_after="2023-05-26">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    The time in ms from when an operation is started until it completes for the
+    {ServiceWorkerCacheSchedulerOpClientType} instance.
+  </summary>
+  <token key="ServiceWorkerCacheSchedulerOpClientType"
+      variants="ServiceWorkerCacheSchedulerOpClientType"/>
+  <token key="ServiceWorkerSchedulerOp" variants="ServiceWorkerSchedulerOp"/>
+</histogram>
+
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheSchedulerOpClientType}.Scheduler.QueueDuration2.{ServiceWorkerSchedulerOp}"
+    units="ms" expires_after="2023-05-26">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    The time in ms from when an operation is started until it completes for the
+    {ServiceWorkerCacheSchedulerOpClientType} instance.
+  </summary>
+  <token key="ServiceWorkerCacheSchedulerOpClientType"
+      variants="ServiceWorkerCacheSchedulerOpClientType"/>
+  <token key="ServiceWorkerSchedulerOp" variants="ServiceWorkerSchedulerOp"/>
+</histogram>
+
+<histogram
+    name="ServiceWorkerCache.{ServiceWorkerCacheSchedulerOpClientType}.Scheduler.QueueLength.{ServiceWorkerSchedulerOp}"
+    units="operations" expires_after="2023-05-26">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    The number of operations in the scheduling queue just before enqueuing a new
+    operation for the {ServiceWorkerCacheSchedulerOpClientType} instance.
+  </summary>
+  <token key="ServiceWorkerCacheSchedulerOpClientType"
+      variants="ServiceWorkerCacheSchedulerOpClientType"/>
+  <token key="ServiceWorkerSchedulerOp" variants="ServiceWorkerSchedulerOp"/>
 </histogram>
 
 </histograms>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index 8d3c1fb6..36e1114 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -262,7 +262,7 @@
 </histogram>
 
 <histogram name="Storage.FileSystemAccess.PersistedPermissions.Age.{Type}"
-    units="ms" expires_after="2023-01-01">
+    units="ms" expires_after="2024-05-01">
   <owner>asully@chromium.org</owner>
   <owner>src/content/browser/file_system_access/OWNERS</owner>
   <summary>
@@ -276,7 +276,7 @@
 </histogram>
 
 <histogram name="Storage.FileSystemAccess.PersistedPermissions.Count"
-    units="paths" expires_after="2023-01-01">
+    units="paths" expires_after="2024-05-01">
   <owner>asully@chromium.org</owner>
   <owner>src/content/browser/file_system_access/OWNERS</owner>
   <summary>
@@ -291,7 +291,7 @@
 
 <histogram
     name="Storage.FileSystemAccess.PersistedPermissions.SweepTime.{Type}"
-    units="ms" expires_after="2023-01-01">
+    units="ms" expires_after="2024-05-01">
   <owner>asully@chromium.org</owner>
   <owner>src/content/browser/file_system_access/OWNERS</owner>
   <summary>
@@ -306,7 +306,7 @@
 
 <histogram
     name="Storage.FileSystemAccess.{OpType}PermissionRequestOutcome.{EntryType}"
-    enum="FileSystemAccessPermissionRequestOutcome" expires_after="2023-05-01">
+    enum="FileSystemAccessPermissionRequestOutcome" expires_after="2024-05-01">
   <owner>asully@chromium.org</owner>
   <owner>src/content/browser/file_system_access/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 1ffce4a..40718c7b 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -1579,7 +1579,7 @@
 </histogram>
 
 <histogram name="Tabs.PersistedTabData.Storage.Restore.File"
-    enum="BooleanSuccess" expires_after="2022-11-21">
+    enum="BooleanSuccess" expires_after="2023-11-21">
   <owner>yusufo@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <owner>dtrainor@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 337cee4..9a08ab98 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -2357,6 +2357,25 @@
   </metric>
 </event>
 
+<event name="Autofill.Sectioning">
+  <owner>fleimgruber@google.com</owner>
+  <owner>schwering@google.com</owner>
+  <summary>
+    Recorded after sectioning is performed when the server classification
+    arrived, and only when `AutofillUseParameterizedSectioning` is enabled.
+  </summary>
+  <metric name="FormSignature">
+    <summary>
+      A 10-bit hash of the form's signature.
+    </summary>
+  </metric>
+  <metric name="SectioningSignature">
+    <summary>
+      A 10-bit hash of the form's computed sections.
+    </summary>
+  </metric>
+</event>
+
 <event name="Autofill.SelectedMaskedServerCard">
   <obsolete>
     Deprecated 2/2019
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 771f5a2..5fa6706 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -2,13 +2,13 @@
 See the following link for directions for making changes to this data:,https://bit.ly/update-benchmarks-info
 Googlers can view additional information about internal perf infrastructure at,https://goto.google.com/chrome-benchmarking-sheet
 Benchmark name,Individual owners,Component,Documentation,Tags
-UNSCHEDULED_ad_frames.fencedframe,lbrady@google.com,Blink>FencedFrames,https://tinyurl.com/fenced-frame-benchmark,
 UNSCHEDULED_ad_frames.iframe,lbrady@google.com,Blink>FencedFrames,https://tinyurl.com/fenced-frame-benchmark,
 UNSCHEDULED_blink_perf.performance_apis,yoavweiss@chromium.org,Blink>PerformanceAPIs,https://bit.ly/blink-perf-benchmarks,all
 UNSCHEDULED_blink_perf.service_worker,"shimazu@chromium.org, falken@chromium.org, ting.shao@intel.com",Blink>ServiceWorker,https://bit.ly/blink-perf-benchmarks,
 UNSCHEDULED_loading.mbi,blink-isolation-dev@chromium.org,Blink>Internals>Modularization,https://bit.ly/loading-benchmarks,many_agents
 UNSCHEDULED_v8.loading_desktop,"cbruni@chromium.org, tmrts@chromium.org, almuthanna@chromium.org",Blink>JavaScript,https://bit.ly/system-health-v8-benchmarks,"2016,2018,2019,2020,emerging_market,health_check,international,javascript_heavy"
 UNSCHEDULED_v8.loading_mobile,"cbruni@chromium.org, leszeks@chromium.org, tmrts@chromium.org",Blink>JavaScript,https://bit.ly/system-health-v8-benchmarks,"2016,2018,2019,2020,emerging_market,health_check,international,javascript_heavy"
+ad_frames.fencedframe,lbrady@google.com,Blink>FencedFrames,https://tinyurl.com/fenced-frame-benchmark,
 base_perftests,"skyostil@chromium.org, gab@chromium.org",Internals>SequenceManager,https://chromium.googlesource.com/chromium/src/+/HEAD/base/README.md#performance-testing,
 blink_perf.accessibility,aleventhal@chromium.org,Blink>Accessibility,https://bit.ly/blink-perf-benchmarks,all
 blink_perf.bindings,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",Blink>Bindings,https://bit.ly/blink-perf-benchmarks,all
diff --git a/tools/perf/benchmarks/ad_frames.py b/tools/perf/benchmarks/ad_frames.py
index 4733294..142465f 100644
--- a/tools/perf/benchmarks/ad_frames.py
+++ b/tools/perf/benchmarks/ad_frames.py
@@ -49,7 +49,7 @@
 
   @classmethod
   def Name(cls):
-    return 'UNSCHEDULED_ad_frames.fencedframe'
+    return 'ad_frames.fencedframe'
 
 
 @benchmark.Info(emails=['lbrady@google.com'],
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 647ba8f..cbe6e1a 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 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "4b26afafb275d583436571f34abd081ffd127235",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/06921e5027e463675d71b2f6978cb3cda5ed5d14/trace_processor_shell.exe"
+            "hash": "2710fe3acd46370f2ff4d4634db0d783f44121a2",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d8f0dc3d20c814236d97a042f06ffacd8f2d6b1f/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "d86ef1c9c3bacf89132506cb29a024e9ad9e1d75",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/06921e5027e463675d71b2f6978cb3cda5ed5d14/trace_processor_shell"
+            "hash": "0692cf90a8a019c0fb2a77426517793a197313b7",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/d8f0dc3d20c814236d97a042f06ffacd8f2d6b1f/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "03d441f184e4f7fbd144de28cb92db2d90170b48",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/06921e5027e463675d71b2f6978cb3cda5ed5d14/trace_processor_shell"
+            "hash": "4bc08289d402692530fea7363630f5914fda63b5",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f5b6c4ed868219aefe172ac2d5bd683814a5f001/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/android-pixel2-perf-calibration_map.json b/tools/perf/core/shard_maps/android-pixel2-perf-calibration_map.json
index 0ffc24b2..3cd538c 100644
--- a/tools/perf/core/shard_maps/android-pixel2-perf-calibration_map.json
+++ b/tools/perf/core/shard_maps/android-pixel2-perf-calibration_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 19,
+                "end": 5,
                 "abridged": false
             }
         }
@@ -13,20 +16,20 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 19,
-                "abridged": false
-            },
-            "blink_perf.css": {
-                "end": 6,
+                "begin": 5,
+                "end": 34,
                 "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 34,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 6,
-                "end": 44,
+                "end": 12,
                 "abridged": false
             }
         }
@@ -34,38 +37,38 @@
     "3": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 44,
-                "abridged": false
-            },
-            "blink_perf.dom": {
-                "abridged": false
-            },
-            "blink_perf.events": {
-                "abridged": false
-            },
-            "blink_perf.image_decoder": {
-                "abridged": false
-            },
-            "blink_perf.layout": {
-                "end": 3,
+                "begin": 12,
+                "end": 41,
                 "abridged": false
             }
         }
     },
     "4": {
         "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 3,
-                "end": 41,
+            "blink_perf.css": {
+                "begin": 41,
+                "abridged": false
+            },
+            "blink_perf.dom": {
+                "abridged": false
+            },
+            "blink_perf.events": {
+                "end": 1,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
+            "blink_perf.events": {
+                "begin": 1,
+                "abridged": false
+            },
+            "blink_perf.image_decoder": {
+                "abridged": false
+            },
             "blink_perf.layout": {
-                "begin": 41,
-                "end": 79,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -73,69 +76,95 @@
     "6": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 79,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "end": 6,
+                "begin": 13,
+                "end": 42,
                 "abridged": false
             }
         }
     },
     "7": {
         "benchmarks": {
-            "blink_perf.paint": {
-                "begin": 6,
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 28,
+            "blink_perf.layout": {
+                "begin": 42,
+                "end": 71,
                 "abridged": false
             }
         }
     },
     "8": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 28,
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "end": 35,
+            "blink_perf.layout": {
+                "begin": 71,
+                "end": 100,
                 "abridged": false
             }
         }
     },
     "9": {
         "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "begin": 35,
+            "blink_perf.layout": {
+                "begin": 100,
                 "abridged": false
             },
-            "blink_perf.svg": {
+            "blink_perf.owp_storage": {
                 "abridged": false
             },
-            "blink_perf.webaudio": {
-                "abridged": false
-            },
-            "blink_perf.webcodecs": {
-                "end": 1,
+            "blink_perf.paint": {
                 "abridged": false
             }
         }
     },
     "10": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "end": 29,
+                "abridged": false
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 29,
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 28,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "begin": 28,
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "end": 18,
+                "abridged": false
+            }
+        }
+    },
+    "13": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 18,
+                "abridged": false
+            },
+            "blink_perf.webaudio": {
+                "abridged": false
+            },
             "blink_perf.webcodecs": {
-                "begin": 1,
                 "abridged": false
             },
             "blink_perf.webgl": {
                 "abridged": false
-            },
+            }
+        }
+    },
+    "14": {
+        "benchmarks": {
             "blink_perf.webgl_fast_call": {
                 "abridged": false
             },
@@ -155,7 +184,7 @@
                 "abridged": false
             },
             "loading.mobile": {
-                "end": 12,
+                "end": 14,
                 "abridged": false
             }
         },
@@ -168,95 +197,59 @@
             }
         }
     },
-    "11": {
-        "benchmarks": {
-            "loading.mobile": {
-                "begin": 12,
-                "end": 50,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "loading.mobile": {
-                "begin": 50,
-                "end": 88,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "loading.mobile": {
-                "begin": 88,
-                "abridged": false
-            },
-            "media.mobile": {
-                "abridged": false
-            },
-            "octane": {
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "end": 13,
-                "abridged": false
-            }
-        }
-    },
-    "14": {
-        "benchmarks": {
-            "rasterize_and_record_micro.top_25": {
-                "begin": 13,
-                "abridged": false
-            },
-            "rendering.mobile": {
-                "end": 26,
-                "abridged": false
-            }
-        }
-    },
     "15": {
         "benchmarks": {
-            "rendering.mobile": {
-                "begin": 26,
-                "end": 64,
+            "loading.mobile": {
+                "begin": 14,
+                "end": 43,
                 "abridged": false
             }
         }
     },
     "16": {
         "benchmarks": {
-            "rendering.mobile": {
-                "begin": 64,
-                "end": 102,
+            "loading.mobile": {
+                "begin": 43,
+                "end": 73,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.mobile": {
-                "begin": 102,
-                "end": 140,
+            "loading.mobile": {
+                "begin": 73,
+                "abridged": false
+            },
+            "media.mobile": {
+                "end": 6,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.mobile": {
-                "begin": 140,
-                "end": 178,
+            "media.mobile": {
+                "begin": 6,
+                "abridged": false
+            },
+            "octane": {
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "end": 19,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
+            "rasterize_and_record_micro.top_25": {
+                "begin": 19,
+                "abridged": false
+            },
             "rendering.mobile": {
-                "begin": 178,
-                "end": 216,
+                "end": 23,
                 "abridged": false
             }
         }
@@ -264,8 +257,8 @@
     "20": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 216,
-                "end": 254,
+                "begin": 23,
+                "end": 53,
                 "abridged": false
             }
         }
@@ -273,8 +266,8 @@
     "21": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 254,
-                "end": 292,
+                "begin": 53,
+                "end": 82,
                 "abridged": false
             }
         }
@@ -282,8 +275,8 @@
     "22": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 292,
-                "end": 331,
+                "begin": 82,
+                "end": 112,
                 "abridged": false
             }
         }
@@ -291,8 +284,8 @@
     "23": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 331,
-                "end": 369,
+                "begin": 112,
+                "end": 141,
                 "abridged": false
             }
         }
@@ -300,8 +293,8 @@
     "24": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 369,
-                "end": 408,
+                "begin": 141,
+                "end": 171,
                 "abridged": false
             }
         }
@@ -309,100 +302,82 @@
     "25": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 408,
-                "abridged": false
-            },
-            "rendering.mobile.notracing": {
-                "end": 25,
+                "begin": 171,
+                "end": 200,
                 "abridged": false
             }
         }
     },
     "26": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 25,
-                "end": 64,
+            "rendering.mobile": {
+                "begin": 200,
+                "end": 230,
                 "abridged": false
             }
         }
     },
     "27": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 64,
-                "end": 102,
+            "rendering.mobile": {
+                "begin": 230,
+                "end": 259,
                 "abridged": false
             }
         }
     },
     "28": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 102,
-                "end": 141,
+            "rendering.mobile": {
+                "begin": 259,
+                "end": 289,
                 "abridged": false
             }
         }
     },
     "29": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 141,
-                "end": 179,
+            "rendering.mobile": {
+                "begin": 289,
+                "end": 318,
                 "abridged": false
             }
         }
     },
     "30": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 179,
-                "end": 218,
+            "rendering.mobile": {
+                "begin": 318,
+                "end": 348,
                 "abridged": false
             }
         }
     },
     "31": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 218,
-                "end": 256,
+            "rendering.mobile": {
+                "begin": 348,
+                "end": 377,
                 "abridged": false
             }
         }
     },
     "32": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 256,
-                "end": 295,
+            "rendering.mobile": {
+                "begin": 377,
+                "end": 407,
                 "abridged": false
             }
         }
     },
     "33": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 295,
-                "end": 333,
+            "rendering.mobile": {
+                "begin": 407,
                 "abridged": false
-            }
-        }
-    },
-    "34": {
-        "benchmarks": {
+            },
             "rendering.mobile.notracing": {
-                "begin": 333,
-                "end": 372,
-                "abridged": false
-            }
-        }
-    },
-    "35": {
-        "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 372,
                 "abridged": false
             },
             "speedometer": {
@@ -424,7 +399,25 @@
                 "abridged": false
             },
             "system_health.common_mobile": {
-                "end": 9,
+                "end": 4,
+                "abridged": false
+            }
+        }
+    },
+    "34": {
+        "benchmarks": {
+            "system_health.common_mobile": {
+                "begin": 4,
+                "end": 34,
+                "abridged": false
+            }
+        }
+    },
+    "35": {
+        "benchmarks": {
+            "system_health.common_mobile": {
+                "begin": 34,
+                "end": 63,
                 "abridged": false
             }
         }
@@ -432,20 +425,20 @@
     "36": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 9,
-                "end": 48,
+                "begin": 63,
+                "abridged": false
+            },
+            "system_health.memory_mobile": {
+                "end": 18,
                 "abridged": false
             }
         }
     },
     "37": {
         "benchmarks": {
-            "system_health.common_mobile": {
-                "begin": 48,
-                "abridged": false
-            },
             "system_health.memory_mobile": {
-                "end": 11,
+                "begin": 18,
+                "end": 47,
                 "abridged": false
             }
         }
@@ -453,16 +446,7 @@
     "38": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 11,
-                "end": 50,
-                "abridged": false
-            }
-        }
-    },
-    "39": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 50,
+                "begin": 47,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -470,12 +454,16 @@
             },
             "system_health.webview_startup": {
                 "abridged": false
-            },
+            }
+        }
+    },
+    "39": {
+        "benchmarks": {
             "tracing.tracing_with_background_memory_infra": {
                 "abridged": false
             },
             "v8.browsing_mobile": {
-                "end": 2,
+                "end": 20,
                 "abridged": false
             }
         }
@@ -483,11 +471,11 @@
     "40": {
         "benchmarks": {
             "v8.browsing_mobile": {
-                "begin": 2,
+                "begin": 20,
                 "abridged": false
             },
             "v8.browsing_mobile-future": {
-                "end": 10,
+                "end": 19,
                 "abridged": false
             }
         }
@@ -495,7 +483,7 @@
     "41": {
         "benchmarks": {
             "v8.browsing_mobile-future": {
-                "begin": 10,
+                "begin": 19,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -507,52 +495,52 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1601,
-        "predicted_min_shard_time": 380,
+        "num_stories": 1226,
+        "predicted_min_shard_time": 290,
         "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 390,
-        "predicted_max_shard_index": 22,
-        "shard #0": 380,
-        "shard #1": 380,
-        "shard #2": 380,
-        "shard #3": 380,
-        "shard #4": 380,
-        "shard #5": 380,
-        "shard #6": 380,
-        "shard #7": 380,
-        "shard #8": 380,
-        "shard #9": 380,
-        "shard #10": 380.0,
-        "shard #11": 380,
-        "shard #12": 380,
-        "shard #13": 380,
-        "shard #14": 380,
-        "shard #15": 380,
-        "shard #16": 380,
-        "shard #17": 380,
-        "shard #18": 380,
-        "shard #19": 380,
-        "shard #20": 380,
-        "shard #21": 380,
-        "shard #22": 390,
-        "shard #23": 380,
-        "shard #24": 390,
-        "shard #25": 380,
-        "shard #26": 390,
-        "shard #27": 380,
-        "shard #28": 390,
-        "shard #29": 380,
-        "shard #30": 390,
-        "shard #31": 380,
-        "shard #32": 390,
-        "shard #33": 380,
-        "shard #34": 390,
-        "shard #35": 380,
-        "shard #36": 390,
-        "shard #37": 380,
-        "shard #38": 390,
-        "shard #39": 380,
-        "shard #40": 390,
-        "shard #41": 380
+        "predicted_max_shard_time": 300,
+        "predicted_max_shard_index": 16,
+        "shard #0": 290,
+        "shard #1": 290,
+        "shard #2": 290,
+        "shard #3": 290,
+        "shard #4": 290,
+        "shard #5": 290,
+        "shard #6": 290,
+        "shard #7": 290,
+        "shard #8": 290,
+        "shard #9": 290,
+        "shard #10": 290,
+        "shard #11": 290,
+        "shard #12": 290,
+        "shard #13": 290,
+        "shard #14": 290.0,
+        "shard #15": 290,
+        "shard #16": 300,
+        "shard #17": 290,
+        "shard #18": 300,
+        "shard #19": 290,
+        "shard #20": 300,
+        "shard #21": 290,
+        "shard #22": 300,
+        "shard #23": 290,
+        "shard #24": 300,
+        "shard #25": 290,
+        "shard #26": 300,
+        "shard #27": 290,
+        "shard #28": 300,
+        "shard #29": 290,
+        "shard #30": 300,
+        "shard #31": 290,
+        "shard #32": 300,
+        "shard #33": 290,
+        "shard #34": 300,
+        "shard #35": 290,
+        "shard #36": 300,
+        "shard #37": 290,
+        "shard #38": 300,
+        "shard #39": 290,
+        "shard #40": 300,
+        "shard #41": 290
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/android-pixel2-perf-pgo_map.json b/tools/perf/core/shard_maps/android-pixel2-perf-pgo_map.json
index 7fe34e55..80a8f5e 100644
--- a/tools/perf/core/shard_maps/android-pixel2-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/android-pixel2-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 38,
+                "end": 20,
                 "abridged": false
             }
         }
@@ -13,11 +16,11 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 38,
+                "begin": 20,
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 44,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -25,7 +28,16 @@
     "2": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 44,
+                "begin": 13,
+                "end": 57,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 57,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -38,16 +50,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 22,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 22,
-                "end": 79,
+                "end": 15,
                 "abridged": false
             }
         }
@@ -55,7 +58,25 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 79,
+                "begin": 15,
+                "end": 59,
+                "abridged": false
+            }
+        }
+    },
+    "5": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 59,
+                "end": 103,
+                "abridged": false
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 103,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -65,27 +86,27 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "end": 9,
+                "end": 18,
                 "abridged": false
             }
         }
     },
-    "5": {
+    "7": {
         "benchmarks": {
             "blink_perf.parser": {
-                "begin": 9,
+                "begin": 18,
                 "abridged": false
             },
             "blink_perf.shadow_dom": {
-                "end": 35,
+                "end": 32,
                 "abridged": false
             }
         }
     },
-    "6": {
+    "8": {
         "benchmarks": {
             "blink_perf.shadow_dom": {
-                "begin": 35,
+                "begin": 32,
                 "abridged": false
             },
             "blink_perf.svg": {
@@ -95,6 +116,15 @@
                 "abridged": false
             },
             "blink_perf.webcodecs": {
+                "end": 4,
+                "abridged": false
+            }
+        }
+    },
+    "9": {
+        "benchmarks": {
+            "blink_perf.webcodecs": {
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.webgl": {
@@ -102,19 +132,7 @@
             },
             "blink_perf.webgl_fast_call": {
                 "abridged": false
-            }
-        },
-        "executables": {
-            "components_perftests": {
-                "arguments": [
-                    "--xvfb"
-                ],
-                "path": "components_perftests"
-            }
-        }
-    },
-    "7": {
-        "benchmarks": {
+            },
             "dummy_benchmark.noisy_benchmark_1": {
                 "abridged": false
             },
@@ -131,27 +149,44 @@
                 "abridged": false
             },
             "loading.mobile": {
-                "end": 52,
+                "end": 19,
+                "abridged": false
+            }
+        },
+        "executables": {
+            "components_perftests": {
+                "arguments": [
+                    "--xvfb"
+                ],
+                "path": "components_perftests"
+            }
+        }
+    },
+    "10": {
+        "benchmarks": {
+            "loading.mobile": {
+                "begin": 19,
+                "end": 63,
                 "abridged": false
             }
         }
     },
-    "8": {
+    "11": {
         "benchmarks": {
             "loading.mobile": {
-                "begin": 52,
+                "begin": 63,
                 "abridged": false
             },
             "media.mobile": {
-                "end": 13,
+                "end": 11,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "12": {
         "benchmarks": {
             "media.mobile": {
-                "begin": 13,
+                "begin": 11,
                 "abridged": false
             },
             "octane": {
@@ -161,34 +196,7 @@
                 "abridged": false
             },
             "rendering.mobile": {
-                "end": 28,
-                "abridged": false
-            }
-        }
-    },
-    "10": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 28,
-                "end": 85,
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 85,
-                "end": 142,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 142,
-                "end": 200,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -196,8 +204,8 @@
     "13": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 200,
-                "end": 257,
+                "begin": 13,
+                "end": 57,
                 "abridged": false
             }
         }
@@ -205,8 +213,8 @@
     "14": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 257,
-                "end": 315,
+                "begin": 57,
+                "end": 101,
                 "abridged": false
             }
         }
@@ -214,8 +222,8 @@
     "15": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 315,
-                "end": 372,
+                "begin": 101,
+                "end": 145,
                 "abridged": false
             }
         }
@@ -223,73 +231,64 @@
     "16": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 372,
-                "abridged": false
-            },
-            "rendering.mobile.notracing": {
-                "end": 9,
+                "begin": 145,
+                "end": 189,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 9,
-                "end": 66,
+            "rendering.mobile": {
+                "begin": 189,
+                "end": 233,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 66,
-                "end": 124,
+            "rendering.mobile": {
+                "begin": 233,
+                "end": 277,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 124,
-                "end": 181,
+            "rendering.mobile": {
+                "begin": 277,
+                "end": 321,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 181,
-                "end": 239,
+            "rendering.mobile": {
+                "begin": 321,
+                "end": 365,
                 "abridged": false
             }
         }
     },
     "21": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 239,
-                "end": 296,
+            "rendering.mobile": {
+                "begin": 365,
+                "end": 409,
                 "abridged": false
             }
         }
     },
     "22": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 296,
-                "end": 354,
+            "rendering.mobile": {
+                "begin": 409,
                 "abridged": false
-            }
-        }
-    },
-    "23": {
-        "benchmarks": {
+            },
             "rendering.mobile.notracing": {
-                "begin": 354,
                 "abridged": false
             },
             "speedometer": {
@@ -311,7 +310,16 @@
                 "abridged": false
             },
             "system_health.common_mobile": {
-                "end": 10,
+                "end": 21,
+                "abridged": false
+            }
+        }
+    },
+    "23": {
+        "benchmarks": {
+            "system_health.common_mobile": {
+                "begin": 21,
+                "end": 65,
                 "abridged": false
             }
         }
@@ -319,28 +327,19 @@
     "24": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 10,
-                "end": 68,
+                "begin": 65,
+                "abridged": false
+            },
+            "system_health.memory_mobile": {
+                "end": 34,
                 "abridged": false
             }
         }
     },
     "25": {
         "benchmarks": {
-            "system_health.common_mobile": {
-                "begin": 68,
-                "abridged": false
-            },
             "system_health.memory_mobile": {
-                "end": 50,
-                "abridged": false
-            }
-        }
-    },
-    "26": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 50,
+                "begin": 34,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -350,21 +349,30 @@
                 "abridged": false
             },
             "tracing.tracing_with_background_memory_infra": {
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "26": {
+        "benchmarks": {
+            "tracing.tracing_with_background_memory_infra": {
+                "begin": 1,
                 "abridged": false
             },
             "v8.browsing_mobile": {
-                "end": 22,
+                "abridged": false
+            },
+            "v8.browsing_mobile-future": {
+                "end": 5,
                 "abridged": false
             }
         }
     },
     "27": {
         "benchmarks": {
-            "v8.browsing_mobile": {
-                "begin": 22,
-                "abridged": false
-            },
             "v8.browsing_mobile-future": {
+                "begin": 5,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -376,38 +384,38 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1601,
-        "predicted_min_shard_time": 570,
-        "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 590.0,
-        "predicted_max_shard_index": 6,
-        "shard #0": 570,
-        "shard #1": 570,
-        "shard #2": 570,
-        "shard #3": 570,
-        "shard #4": 570,
-        "shard #5": 570,
-        "shard #6": 590.0,
-        "shard #7": 570,
-        "shard #8": 570,
-        "shard #9": 570,
-        "shard #10": 570,
-        "shard #11": 570,
-        "shard #12": 580,
-        "shard #13": 570,
-        "shard #14": 580,
-        "shard #15": 570,
-        "shard #16": 580,
-        "shard #17": 570,
-        "shard #18": 580,
-        "shard #19": 570,
-        "shard #20": 580,
-        "shard #21": 570,
-        "shard #22": 580,
-        "shard #23": 570,
-        "shard #24": 580,
-        "shard #25": 570,
-        "shard #26": 580,
-        "shard #27": 570
+        "num_stories": 1226,
+        "predicted_min_shard_time": 430,
+        "predicted_min_shard_index": 27,
+        "predicted_max_shard_time": 440,
+        "predicted_max_shard_index": 0,
+        "shard #0": 440,
+        "shard #1": 440,
+        "shard #2": 440,
+        "shard #3": 440,
+        "shard #4": 440,
+        "shard #5": 440,
+        "shard #6": 440,
+        "shard #7": 440,
+        "shard #8": 440,
+        "shard #9": 440.0,
+        "shard #10": 440,
+        "shard #11": 440,
+        "shard #12": 440,
+        "shard #13": 440,
+        "shard #14": 440,
+        "shard #15": 440,
+        "shard #16": 440,
+        "shard #17": 440,
+        "shard #18": 440,
+        "shard #19": 440,
+        "shard #20": 440,
+        "shard #21": 440,
+        "shard #22": 440,
+        "shard #23": 440,
+        "shard #24": 440,
+        "shard #25": 440,
+        "shard #26": 440,
+        "shard #27": 430
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/android-pixel2-perf_map.json b/tools/perf/core/shard_maps/android-pixel2-perf_map.json
index d6291c4..a37b039 100644
--- a/tools/perf/core/shard_maps/android-pixel2-perf_map.json
+++ b/tools/perf/core/shard_maps/android-pixel2-perf_map.json
@@ -85,8 +85,11 @@
             "startup.mobile": {
                 "abridged": false
             },
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
-                "end": 17,
+                "end": 16,
                 "abridged": false
             },
             "speedometer2": {
@@ -154,14 +157,14 @@
                 "abridged": false
             },
             "blink_perf.accessibility": {
-                "begin": 17,
+                "begin": 16,
                 "abridged": false
             },
             "blink_perf.bindings": {
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 40,
+                "end": 37,
                 "abridged": false
             }
         }
@@ -209,7 +212,7 @@
                 "abridged": false
             },
             "blink_perf.css": {
-                "begin": 40,
+                "begin": 37,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -222,7 +225,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 49,
+                "end": 46,
                 "abridged": false
             }
         }
@@ -233,7 +236,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "begin": 49,
+                "begin": 46,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -455,7 +458,7 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1568,
+        "num_stories": 1575,
         "predicted_min_shard_time": 1927.0,
         "predicted_min_shard_index": 17,
         "predicted_max_shard_time": 2551.0,
@@ -465,18 +468,18 @@
         "shard #2": 1965.0,
         "shard #3": 2067.0,
         "shard #4": 2127.0,
-        "shard #5": 2066.0,
+        "shard #5": 2065.0,
         "shard #6": 2068.0,
         "shard #7": 2173.0,
         "shard #8": 2086.0,
         "shard #9": 2077.0,
-        "shard #10": 2070.0,
+        "shard #10": 2081.0,
         "shard #11": 2063.0,
         "shard #12": 2062.0,
         "shard #13": 2103.0,
         "shard #14": 2112.0,
-        "shard #15": 2075.0,
-        "shard #16": 2070.0,
+        "shard #15": 2080.0,
+        "shard #16": 2125.0,
         "shard #17": 1927.0,
         "shard #18": 2070.0,
         "shard #19": 2103.0,
diff --git a/tools/perf/core/shard_maps/android-pixel2_webview-perf-pgo_map.json b/tools/perf/core/shard_maps/android-pixel2_webview-perf-pgo_map.json
index 5777258..8ac33a3 100644
--- a/tools/perf/core/shard_maps/android-pixel2_webview-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/android-pixel2_webview-perf-pgo_map.json
@@ -1,22 +1,34 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "abridged": false
-            },
-            "blink_perf.css": {
-                "end": 5,
+                "end": 33,
                 "abridged": false
             }
         }
     },
     "1": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 33,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 5,
+                "end": 39,
+                "abridged": false
+            }
+        }
+    },
+    "2": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 39,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -29,16 +41,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 1,
-                "abridged": false
-            }
-        }
-    },
-    "2": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 1,
-                "end": 76,
+                "end": 10,
                 "abridged": false
             }
         }
@@ -46,46 +49,64 @@
     "3": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 76,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 24,
+                "begin": 10,
+                "end": 67,
                 "abridged": false
             }
         }
     },
     "4": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 24,
+            "blink_perf.layout": {
+                "begin": 67,
                 "abridged": false
             },
-            "blink_perf.shadow_dom": {
+            "blink_perf.owp_storage": {
                 "abridged": false
             },
-            "blink_perf.svg": {
-                "abridged": false
-            },
-            "blink_perf.webaudio": {
-                "end": 5,
+            "blink_perf.paint": {
+                "end": 12,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
+            "blink_perf.paint": {
+                "begin": 12,
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 22,
+                "abridged": false
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "begin": 22,
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "abridged": false
+            },
             "blink_perf.webaudio": {
-                "begin": 5,
                 "abridged": false
             },
             "blink_perf.webcodecs": {
+                "end": 7,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.webcodecs": {
+                "begin": 7,
                 "abridged": false
             },
             "blink_perf.webgl": {
@@ -107,55 +128,37 @@
                 "abridged": false
             },
             "loading.mobile": {
-                "end": 51,
-                "abridged": false
-            }
-        }
-    },
-    "6": {
-        "benchmarks": {
-            "loading.mobile": {
-                "begin": 51,
-                "abridged": false
-            },
-            "media.mobile": {
-                "abridged": false
-            },
-            "octane": {
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "end": 13,
-                "abridged": false
-            }
-        }
-    },
-    "7": {
-        "benchmarks": {
-            "rasterize_and_record_micro.top_25": {
-                "begin": 13,
-                "abridged": false
-            },
-            "rendering.mobile": {
-                "end": 63,
+                "end": 42,
                 "abridged": false
             }
         }
     },
     "8": {
         "benchmarks": {
-            "rendering.mobile": {
-                "begin": 63,
-                "end": 137,
+            "loading.mobile": {
+                "begin": 42,
+                "abridged": false
+            },
+            "media.mobile": {
+                "end": 3,
                 "abridged": false
             }
         }
     },
     "9": {
         "benchmarks": {
+            "media.mobile": {
+                "begin": 3,
+                "abridged": false
+            },
+            "octane": {
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.mobile": {
-                "begin": 137,
-                "end": 212,
+                "end": 18,
                 "abridged": false
             }
         }
@@ -163,8 +166,8 @@
     "10": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 212,
-                "end": 286,
+                "begin": 18,
+                "end": 75,
                 "abridged": false
             }
         }
@@ -172,8 +175,8 @@
     "11": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 286,
-                "end": 361,
+                "begin": 75,
+                "end": 132,
                 "abridged": false
             }
         }
@@ -181,64 +184,55 @@
     "12": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 361,
-                "abridged": false
-            },
-            "rendering.mobile.notracing": {
-                "end": 14,
+                "begin": 132,
+                "end": 189,
                 "abridged": false
             }
         }
     },
     "13": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 14,
-                "end": 89,
+            "rendering.mobile": {
+                "begin": 189,
+                "end": 246,
                 "abridged": false
             }
         }
     },
     "14": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 89,
-                "end": 163,
+            "rendering.mobile": {
+                "begin": 246,
+                "end": 302,
                 "abridged": false
             }
         }
     },
     "15": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 163,
-                "end": 238,
+            "rendering.mobile": {
+                "begin": 302,
+                "end": 359,
                 "abridged": false
             }
         }
     },
     "16": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 238,
-                "end": 312,
+            "rendering.mobile": {
+                "begin": 359,
+                "end": 415,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 312,
-                "end": 387,
+            "rendering.mobile": {
+                "begin": 415,
                 "abridged": false
-            }
-        }
-    },
-    "18": {
-        "benchmarks": {
+            },
             "rendering.mobile.notracing": {
-                "begin": 387,
                 "abridged": false
             },
             "speedometer": {
@@ -260,27 +254,27 @@
                 "abridged": false
             },
             "system_health.common_mobile": {
-                "end": 60,
+                "end": 40,
+                "abridged": false
+            }
+        }
+    },
+    "18": {
+        "benchmarks": {
+            "system_health.common_mobile": {
+                "begin": 40,
+                "abridged": false
+            },
+            "system_health.memory_mobile": {
+                "end": 21,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "system_health.common_mobile": {
-                "begin": 60,
-                "abridged": false
-            },
             "system_health.memory_mobile": {
-                "end": 60,
-                "abridged": false
-            }
-        }
-    },
-    "20": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 60,
+                "begin": 21,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -290,6 +284,15 @@
                 "abridged": false
             },
             "tracing.tracing_with_background_memory_infra": {
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "20": {
+        "benchmarks": {
+            "tracing.tracing_with_background_memory_infra": {
+                "begin": 1,
                 "abridged": false
             },
             "v8.browsing_mobile": {
@@ -304,31 +307,31 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1568,
-        "predicted_min_shard_time": 740,
-        "predicted_min_shard_index": 8,
-        "predicted_max_shard_time": 750,
+        "num_stories": 1193,
+        "predicted_min_shard_time": 560,
+        "predicted_min_shard_index": 14,
+        "predicted_max_shard_time": 570,
         "predicted_max_shard_index": 0,
-        "shard #0": 750,
-        "shard #1": 750,
-        "shard #2": 750,
-        "shard #3": 750,
-        "shard #4": 750,
-        "shard #5": 750,
-        "shard #6": 750,
-        "shard #7": 750,
-        "shard #8": 740,
-        "shard #9": 750,
-        "shard #10": 740,
-        "shard #11": 750,
-        "shard #12": 740,
-        "shard #13": 750,
-        "shard #14": 740,
-        "shard #15": 750,
-        "shard #16": 740,
-        "shard #17": 750,
-        "shard #18": 740,
-        "shard #19": 750,
-        "shard #20": 740
+        "shard #0": 570,
+        "shard #1": 570,
+        "shard #2": 570,
+        "shard #3": 570,
+        "shard #4": 570,
+        "shard #5": 570,
+        "shard #6": 570,
+        "shard #7": 570,
+        "shard #8": 570,
+        "shard #9": 570,
+        "shard #10": 570,
+        "shard #11": 570,
+        "shard #12": 570,
+        "shard #13": 570,
+        "shard #14": 560,
+        "shard #15": 570,
+        "shard #16": 560,
+        "shard #17": 570,
+        "shard #18": 560,
+        "shard #19": 570,
+        "shard #20": 560
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/android-pixel2_webview-perf_map.json b/tools/perf/core/shard_maps/android-pixel2_webview-perf_map.json
index a8ff77d..9a1c9d0 100644
--- a/tools/perf/core/shard_maps/android-pixel2_webview-perf_map.json
+++ b/tools/perf/core/shard_maps/android-pixel2_webview-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -8,7 +11,7 @@
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 9,
+                "end": 4,
                 "abridged": false
             }
         }
@@ -16,7 +19,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 9,
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -29,7 +32,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 73,
+                "end": 67,
                 "abridged": false
             }
         }
@@ -37,7 +40,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 73,
+                "begin": 67,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -299,14 +302,14 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1186,
+        "num_stories": 1193,
         "predicted_min_shard_time": 1465.0,
         "predicted_min_shard_index": 19,
-        "predicted_max_shard_time": 1560.0,
-        "predicted_max_shard_index": 18,
-        "shard #0": 1520.0,
-        "shard #1": 1535.0,
-        "shard #2": 1528.0,
+        "predicted_max_shard_time": 1590.0,
+        "predicted_max_shard_index": 2,
+        "shard #0": 1529.0,
+        "shard #1": 1534.0,
+        "shard #2": 1590.0,
         "shard #3": 1537.0,
         "shard #4": 1524.0,
         "shard #5": 1518.0,
diff --git a/tools/perf/core/shard_maps/android-pixel4-perf-pgo_map.json b/tools/perf/core/shard_maps/android-pixel4-perf-pgo_map.json
index 7fe34e55..80a8f5e 100644
--- a/tools/perf/core/shard_maps/android-pixel4-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/android-pixel4-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 38,
+                "end": 20,
                 "abridged": false
             }
         }
@@ -13,11 +16,11 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 38,
+                "begin": 20,
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 44,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -25,7 +28,16 @@
     "2": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 44,
+                "begin": 13,
+                "end": 57,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 57,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -38,16 +50,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 22,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 22,
-                "end": 79,
+                "end": 15,
                 "abridged": false
             }
         }
@@ -55,7 +58,25 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 79,
+                "begin": 15,
+                "end": 59,
+                "abridged": false
+            }
+        }
+    },
+    "5": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 59,
+                "end": 103,
+                "abridged": false
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 103,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -65,27 +86,27 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "end": 9,
+                "end": 18,
                 "abridged": false
             }
         }
     },
-    "5": {
+    "7": {
         "benchmarks": {
             "blink_perf.parser": {
-                "begin": 9,
+                "begin": 18,
                 "abridged": false
             },
             "blink_perf.shadow_dom": {
-                "end": 35,
+                "end": 32,
                 "abridged": false
             }
         }
     },
-    "6": {
+    "8": {
         "benchmarks": {
             "blink_perf.shadow_dom": {
-                "begin": 35,
+                "begin": 32,
                 "abridged": false
             },
             "blink_perf.svg": {
@@ -95,6 +116,15 @@
                 "abridged": false
             },
             "blink_perf.webcodecs": {
+                "end": 4,
+                "abridged": false
+            }
+        }
+    },
+    "9": {
+        "benchmarks": {
+            "blink_perf.webcodecs": {
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.webgl": {
@@ -102,19 +132,7 @@
             },
             "blink_perf.webgl_fast_call": {
                 "abridged": false
-            }
-        },
-        "executables": {
-            "components_perftests": {
-                "arguments": [
-                    "--xvfb"
-                ],
-                "path": "components_perftests"
-            }
-        }
-    },
-    "7": {
-        "benchmarks": {
+            },
             "dummy_benchmark.noisy_benchmark_1": {
                 "abridged": false
             },
@@ -131,27 +149,44 @@
                 "abridged": false
             },
             "loading.mobile": {
-                "end": 52,
+                "end": 19,
+                "abridged": false
+            }
+        },
+        "executables": {
+            "components_perftests": {
+                "arguments": [
+                    "--xvfb"
+                ],
+                "path": "components_perftests"
+            }
+        }
+    },
+    "10": {
+        "benchmarks": {
+            "loading.mobile": {
+                "begin": 19,
+                "end": 63,
                 "abridged": false
             }
         }
     },
-    "8": {
+    "11": {
         "benchmarks": {
             "loading.mobile": {
-                "begin": 52,
+                "begin": 63,
                 "abridged": false
             },
             "media.mobile": {
-                "end": 13,
+                "end": 11,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "12": {
         "benchmarks": {
             "media.mobile": {
-                "begin": 13,
+                "begin": 11,
                 "abridged": false
             },
             "octane": {
@@ -161,34 +196,7 @@
                 "abridged": false
             },
             "rendering.mobile": {
-                "end": 28,
-                "abridged": false
-            }
-        }
-    },
-    "10": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 28,
-                "end": 85,
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 85,
-                "end": 142,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.mobile": {
-                "begin": 142,
-                "end": 200,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -196,8 +204,8 @@
     "13": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 200,
-                "end": 257,
+                "begin": 13,
+                "end": 57,
                 "abridged": false
             }
         }
@@ -205,8 +213,8 @@
     "14": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 257,
-                "end": 315,
+                "begin": 57,
+                "end": 101,
                 "abridged": false
             }
         }
@@ -214,8 +222,8 @@
     "15": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 315,
-                "end": 372,
+                "begin": 101,
+                "end": 145,
                 "abridged": false
             }
         }
@@ -223,73 +231,64 @@
     "16": {
         "benchmarks": {
             "rendering.mobile": {
-                "begin": 372,
-                "abridged": false
-            },
-            "rendering.mobile.notracing": {
-                "end": 9,
+                "begin": 145,
+                "end": 189,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 9,
-                "end": 66,
+            "rendering.mobile": {
+                "begin": 189,
+                "end": 233,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 66,
-                "end": 124,
+            "rendering.mobile": {
+                "begin": 233,
+                "end": 277,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 124,
-                "end": 181,
+            "rendering.mobile": {
+                "begin": 277,
+                "end": 321,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 181,
-                "end": 239,
+            "rendering.mobile": {
+                "begin": 321,
+                "end": 365,
                 "abridged": false
             }
         }
     },
     "21": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 239,
-                "end": 296,
+            "rendering.mobile": {
+                "begin": 365,
+                "end": 409,
                 "abridged": false
             }
         }
     },
     "22": {
         "benchmarks": {
-            "rendering.mobile.notracing": {
-                "begin": 296,
-                "end": 354,
+            "rendering.mobile": {
+                "begin": 409,
                 "abridged": false
-            }
-        }
-    },
-    "23": {
-        "benchmarks": {
+            },
             "rendering.mobile.notracing": {
-                "begin": 354,
                 "abridged": false
             },
             "speedometer": {
@@ -311,7 +310,16 @@
                 "abridged": false
             },
             "system_health.common_mobile": {
-                "end": 10,
+                "end": 21,
+                "abridged": false
+            }
+        }
+    },
+    "23": {
+        "benchmarks": {
+            "system_health.common_mobile": {
+                "begin": 21,
+                "end": 65,
                 "abridged": false
             }
         }
@@ -319,28 +327,19 @@
     "24": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 10,
-                "end": 68,
+                "begin": 65,
+                "abridged": false
+            },
+            "system_health.memory_mobile": {
+                "end": 34,
                 "abridged": false
             }
         }
     },
     "25": {
         "benchmarks": {
-            "system_health.common_mobile": {
-                "begin": 68,
-                "abridged": false
-            },
             "system_health.memory_mobile": {
-                "end": 50,
-                "abridged": false
-            }
-        }
-    },
-    "26": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 50,
+                "begin": 34,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -350,21 +349,30 @@
                 "abridged": false
             },
             "tracing.tracing_with_background_memory_infra": {
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "26": {
+        "benchmarks": {
+            "tracing.tracing_with_background_memory_infra": {
+                "begin": 1,
                 "abridged": false
             },
             "v8.browsing_mobile": {
-                "end": 22,
+                "abridged": false
+            },
+            "v8.browsing_mobile-future": {
+                "end": 5,
                 "abridged": false
             }
         }
     },
     "27": {
         "benchmarks": {
-            "v8.browsing_mobile": {
-                "begin": 22,
-                "abridged": false
-            },
             "v8.browsing_mobile-future": {
+                "begin": 5,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -376,38 +384,38 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1601,
-        "predicted_min_shard_time": 570,
-        "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 590.0,
-        "predicted_max_shard_index": 6,
-        "shard #0": 570,
-        "shard #1": 570,
-        "shard #2": 570,
-        "shard #3": 570,
-        "shard #4": 570,
-        "shard #5": 570,
-        "shard #6": 590.0,
-        "shard #7": 570,
-        "shard #8": 570,
-        "shard #9": 570,
-        "shard #10": 570,
-        "shard #11": 570,
-        "shard #12": 580,
-        "shard #13": 570,
-        "shard #14": 580,
-        "shard #15": 570,
-        "shard #16": 580,
-        "shard #17": 570,
-        "shard #18": 580,
-        "shard #19": 570,
-        "shard #20": 580,
-        "shard #21": 570,
-        "shard #22": 580,
-        "shard #23": 570,
-        "shard #24": 580,
-        "shard #25": 570,
-        "shard #26": 580,
-        "shard #27": 570
+        "num_stories": 1226,
+        "predicted_min_shard_time": 430,
+        "predicted_min_shard_index": 27,
+        "predicted_max_shard_time": 440,
+        "predicted_max_shard_index": 0,
+        "shard #0": 440,
+        "shard #1": 440,
+        "shard #2": 440,
+        "shard #3": 440,
+        "shard #4": 440,
+        "shard #5": 440,
+        "shard #6": 440,
+        "shard #7": 440,
+        "shard #8": 440,
+        "shard #9": 440.0,
+        "shard #10": 440,
+        "shard #11": 440,
+        "shard #12": 440,
+        "shard #13": 440,
+        "shard #14": 440,
+        "shard #15": 440,
+        "shard #16": 440,
+        "shard #17": 440,
+        "shard #18": 440,
+        "shard #19": 440,
+        "shard #20": 440,
+        "shard #21": 440,
+        "shard #22": 440,
+        "shard #23": 440,
+        "shard #24": 440,
+        "shard #25": 440,
+        "shard #26": 440,
+        "shard #27": 430
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/android-pixel4-perf_map.json b/tools/perf/core/shard_maps/android-pixel4-perf_map.json
index b103e22a..8da9b714 100644
--- a/tools/perf/core/shard_maps/android-pixel4-perf_map.json
+++ b/tools/perf/core/shard_maps/android-pixel4-perf_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 31,
+                "end": 26,
                 "abridged": false
             },
             "jetstream2": {
@@ -19,7 +22,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 31,
+                "begin": 26,
                 "abridged": false
             },
             "blink_perf.css": {
@@ -32,10 +35,7 @@
                 "abridged": false
             },
             "blink_perf.image_decoder": {
-                "abridged": false
-            },
-            "blink_perf.layout": {
-                "end": 4,
+                "end": 9,
                 "abridged": false
             },
             "jetstream2": {
@@ -48,9 +48,12 @@
     },
     "2": {
         "benchmarks": {
+            "blink_perf.image_decoder": {
+                "begin": 9,
+                "abridged": false
+            },
             "blink_perf.layout": {
-                "begin": 4,
-                "end": 102,
+                "end": 101,
                 "abridged": false
             },
             "jetstream2": {
@@ -64,7 +67,7 @@
     "3": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 102,
+                "begin": 101,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -453,15 +456,15 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1242,
-        "predicted_min_shard_time": 1193.0,
-        "predicted_min_shard_index": 3,
+        "num_stories": 1249,
+        "predicted_min_shard_time": 1206.0,
+        "predicted_min_shard_index": 21,
         "predicted_max_shard_time": 1573.0,
         "predicted_max_shard_index": 27,
-        "shard #0": 1270.0,
-        "shard #1": 1268.0,
-        "shard #2": 1271.0,
-        "shard #3": 1193.0,
+        "shard #0": 1271.0,
+        "shard #1": 1273.0,
+        "shard #2": 1255.0,
+        "shard #3": 1273.0,
         "shard #4": 1312.0,
         "shard #5": 1271.0,
         "shard #6": 1252.0,
diff --git a/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json b/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json
index 9a16aa1a..ae476e3 100644
--- a/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json
+++ b/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -8,7 +11,7 @@
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 15,
+                "end": 6,
                 "abridged": false
             },
             "speedometer2": {
@@ -19,7 +22,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 15,
+                "begin": 6,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -32,7 +35,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 84,
+                "end": 79,
                 "abridged": false
             },
             "speedometer2": {
@@ -43,7 +46,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 84,
+                "begin": 79,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -56,7 +59,7 @@
                 "abridged": false
             },
             "blink_perf.shadow_dom": {
-                "end": 20,
+                "end": 14,
                 "abridged": false
             },
             "speedometer2": {
@@ -67,7 +70,7 @@
     "3": {
         "benchmarks": {
             "blink_perf.shadow_dom": {
-                "begin": 20,
+                "begin": 14,
                 "abridged": false
             },
             "blink_perf.svg": {
@@ -98,7 +101,7 @@
                 "abridged": false
             },
             "loading.mobile": {
-                "end": 20,
+                "end": 18,
                 "abridged": false
             },
             "speedometer2": {
@@ -109,8 +112,8 @@
     "4": {
         "benchmarks": {
             "loading.mobile": {
-                "begin": 20,
-                "end": 59,
+                "begin": 18,
+                "end": 56,
                 "abridged": false
             },
             "speedometer2": {
@@ -121,7 +124,7 @@
     "5": {
         "benchmarks": {
             "loading.mobile": {
-                "begin": 59,
+                "begin": 56,
                 "end": 95,
                 "abridged": false
             },
@@ -356,17 +359,17 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1205,
+        "num_stories": 1212,
         "predicted_min_shard_time": 1277.0,
         "predicted_min_shard_index": 15,
         "predicted_max_shard_time": 1391.0,
         "predicted_max_shard_index": 19,
-        "shard #0": 1330.0,
-        "shard #1": 1328.0,
-        "shard #2": 1334.0,
-        "shard #3": 1327.0,
-        "shard #4": 1330.0,
-        "shard #5": 1320.0,
+        "shard #0": 1339.0,
+        "shard #1": 1337.0,
+        "shard #2": 1345.0,
+        "shard #3": 1334.0,
+        "shard #4": 1346.0,
+        "shard #5": 1338.0,
         "shard #6": 1333.0,
         "shard #7": 1340.0,
         "shard #8": 1335.0,
diff --git a/tools/perf/core/shard_maps/lacros-eve-perf_map.json b/tools/perf/core/shard_maps/lacros-eve-perf_map.json
index de2c614b..44030301 100644
--- a/tools/perf/core/shard_maps/lacros-eve-perf_map.json
+++ b/tools/perf/core/shard_maps/lacros-eve-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -163,12 +166,12 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1199,
+        "num_stories": 1204,
         "predicted_min_shard_time": 25857.0,
         "predicted_min_shard_index": 3,
-        "predicted_max_shard_time": 26028.0,
+        "predicted_max_shard_time": 26078.0,
         "predicted_max_shard_index": 0,
-        "shard #0": 26028.0,
+        "shard #0": 26078.0,
         "shard #1": 25922.0,
         "shard #2": 25909.0,
         "shard #3": 25857.0
diff --git a/tools/perf/core/shard_maps/lacros-x86-perf_map.json b/tools/perf/core/shard_maps/lacros-x86-perf_map.json
index 25ee7cf..60ece26 100644
--- a/tools/perf/core/shard_maps/lacros-x86-perf_map.json
+++ b/tools/perf/core/shard_maps/lacros-x86-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -20,7 +23,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 44,
+                "end": 40,
                 "abridged": false
             }
         }
@@ -28,7 +31,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 44,
+                "begin": 40,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -51,17 +54,12 @@
             },
             "blink_perf.webcodecs": {
                 "abridged": false
-            },
-            "blink_perf.webgl": {
-                "end": 3,
-                "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
             "blink_perf.webgl": {
-                "begin": 3,
                 "abridged": false
             },
             "blink_perf.webgl_fast_call": {
@@ -101,7 +99,7 @@
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
-                "end": 9,
+                "end": 7,
                 "abridged": false
             }
         }
@@ -109,11 +107,11 @@
     "3": {
         "benchmarks": {
             "rasterize_and_record_micro.top_25": {
-                "begin": 9,
+                "begin": 7,
                 "abridged": false
             },
             "rendering.desktop": {
-                "end": 184,
+                "end": 182,
                 "abridged": false
             }
         }
@@ -121,7 +119,7 @@
     "4": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 184,
+                "begin": 182,
                 "abridged": false
             },
             "rendering.desktop.notracing": {
@@ -143,7 +141,7 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 49,
+                "end": 48,
                 "abridged": false
             }
         }
@@ -151,7 +149,7 @@
     "5": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 49,
+                "begin": 48,
                 "abridged": false
             },
             "system_health.memory_desktop": {
@@ -181,16 +179,16 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1199,
-        "predicted_min_shard_time": 1990,
-        "predicted_min_shard_index": 5,
-        "predicted_max_shard_time": 2000,
+        "num_stories": 1204,
+        "predicted_min_shard_time": 2000,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 2010,
         "predicted_max_shard_index": 0,
-        "shard #0": 2000,
-        "shard #1": 2000,
-        "shard #2": 2000,
+        "shard #0": 2010,
+        "shard #1": 2010,
+        "shard #2": 2010,
         "shard #3": 2000,
-        "shard #4": 2000,
-        "shard #5": 1990
+        "shard #4": 2010,
+        "shard #5": 2000
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/linux-perf-calibration_map.json b/tools/perf/core/shard_maps/linux-perf-calibration_map.json
index ef474c4..6e5cc06 100644
--- a/tools/perf/core/shard_maps/linux-perf-calibration_map.json
+++ b/tools/perf/core/shard_maps/linux-perf-calibration_map.json
@@ -4,11 +4,11 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "blink_perf.accessibility": {
+            "ad_frames.fencedframe": {
                 "abridged": false
             },
-            "blink_perf.bindings": {
-                "end": 14,
+            "blink_perf.accessibility": {
+                "end": 17,
                 "abridged": false
             },
             "jetstream2": {
@@ -33,12 +33,12 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "blink_perf.bindings": {
-                "begin": 14,
+            "blink_perf.accessibility": {
+                "begin": 17,
                 "abridged": false
             },
-            "blink_perf.css": {
-                "end": 16,
+            "blink_perf.bindings": {
+                "end": 40,
                 "abridged": false
             },
             "jetstream2": {
@@ -54,18 +54,12 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
+            "blink_perf.bindings": {
+                "begin": 40,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 16,
-                "abridged": false
-            },
-            "blink_perf.dom": {
-                "abridged": false
-            },
-            "blink_perf.events": {
-                "abridged": false
-            },
-            "blink_perf.image_decoder": {
-                "end": 1,
+                "end": 31,
                 "abridged": false
             },
             "jetstream2": {
@@ -81,12 +75,15 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "blink_perf.image_decoder": {
-                "begin": 1,
+            "blink_perf.css": {
+                "begin": 31,
                 "abridged": false
             },
-            "blink_perf.layout": {
-                "end": 44,
+            "blink_perf.dom": {
+                "abridged": false
+            },
+            "blink_perf.events": {
+                "end": 4,
                 "abridged": false
             },
             "jetstream2": {
@@ -102,9 +99,15 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
+            "blink_perf.events": {
+                "begin": 4,
+                "abridged": false
+            },
+            "blink_perf.image_decoder": {
+                "abridged": false
+            },
             "blink_perf.layout": {
-                "begin": 44,
-                "end": 98,
+                "end": 29,
                 "abridged": false
             },
             "jetstream2": {
@@ -121,17 +124,8 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "begin": 98,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 24,
+                "begin": 29,
+                "end": 71,
                 "abridged": false
             },
             "jetstream2": {
@@ -147,26 +141,14 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "blink_perf.parser": {
-                "begin": 24,
+            "blink_perf.layout": {
+                "begin": 71,
                 "abridged": false
             },
-            "blink_perf.sanitizer-api": {
+            "blink_perf.owp_storage": {
                 "abridged": false
             },
-            "blink_perf.svg": {
-                "abridged": false
-            },
-            "blink_perf.webaudio": {
-                "abridged": false
-            },
-            "blink_perf.webcodecs": {
-                "abridged": false
-            },
-            "blink_perf.webgl": {
-                "abridged": false
-            },
-            "blink_perf.webgl_fast_call": {
+            "blink_perf.paint": {
                 "end": 1,
                 "abridged": false
             },
@@ -183,40 +165,20 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "blink_perf.webgl_fast_call": {
+            "blink_perf.paint": {
                 "begin": 1,
                 "abridged": false
             },
-            "desktop_ui": {
-                "abridged": false
-            },
-            "dummy_benchmark.noisy_benchmark_1": {
-                "abridged": false
-            },
-            "dummy_benchmark.stable_benchmark_1": {
-                "abridged": false
-            },
-            "jetstream": {
+            "blink_perf.parser": {
+                "end": 26,
                 "abridged": false
             },
             "jetstream2": {
                 "abridged": false
             },
-            "kraken": {
-                "abridged": false
-            },
-            "loading.desktop": {
-                "end": 11,
-                "abridged": false
-            },
             "speedometer2": {
                 "abridged": false
             }
-        },
-        "executables": {
-            "load_library_perf_tests": {
-                "path": "load_library_perf_tests"
-            }
         }
     },
     "8": {
@@ -224,9 +186,21 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "loading.desktop": {
-                "begin": 11,
-                "end": 65,
+            "blink_perf.parser": {
+                "begin": 26,
+                "abridged": false
+            },
+            "blink_perf.sanitizer-api": {
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "abridged": false
+            },
+            "blink_perf.webaudio": {
+                "abridged": false
+            },
+            "blink_perf.webcodecs": {
+                "end": 4,
                 "abridged": false
             },
             "jetstream2": {
@@ -242,12 +216,20 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "loading.desktop": {
-                "begin": 65,
+            "blink_perf.webcodecs": {
+                "begin": 4,
                 "abridged": false
             },
-            "media.desktop": {
-                "end": 14,
+            "blink_perf.webgl": {
+                "abridged": false
+            },
+            "blink_perf.webgl_fast_call": {
+                "abridged": false
+            },
+            "desktop_ui": {
+                "abridged": false
+            },
+            "dummy_benchmark.noisy_benchmark_1": {
                 "abridged": false
             },
             "jetstream2": {
@@ -263,8 +245,72 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
+            "dummy_benchmark.stable_benchmark_1": {
+                "abridged": false
+            },
+            "jetstream": {
+                "abridged": false
+            },
+            "jetstream2": {
+                "abridged": false
+            },
+            "kraken": {
+                "abridged": false
+            },
+            "loading.desktop": {
+                "end": 39,
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        },
+        "executables": {
+            "load_library_perf_tests": {
+                "path": "load_library_perf_tests"
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "abridged": false
+            },
+            "loading.desktop": {
+                "begin": 39,
+                "end": 82,
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "abridged": false
+            },
+            "loading.desktop": {
+                "begin": 82,
+                "abridged": false
+            },
             "media.desktop": {
-                "begin": 14,
+                "end": 21,
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "13": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "abridged": false
+            },
+            "media.desktop": {
+                "begin": 21,
                 "abridged": false
             },
             "memory.desktop": {
@@ -274,10 +320,7 @@
                 "abridged": false
             },
             "power.desktop": {
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "end": 2,
+                "end": 13,
                 "abridged": false
             },
             "speedometer2": {
@@ -299,62 +342,20 @@
             }
         }
     },
-    "11": {
-        "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "begin": 2,
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "end": 32,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "begin": 32,
-                "end": 86,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "begin": 86,
-                "end": 141,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
     "14": {
         "benchmarks": {
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
+            "power.desktop": {
+                "begin": 13,
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 141,
-                "end": 195,
+                "end": 16,
                 "abridged": false
             },
             "speedometer2": {
@@ -368,8 +369,8 @@
                 "abridged": false
             },
             "rendering.desktop": {
-                "begin": 195,
-                "end": 250,
+                "begin": 16,
+                "end": 60,
                 "abridged": false
             },
             "speedometer2": {
@@ -383,8 +384,8 @@
                 "abridged": false
             },
             "rendering.desktop": {
-                "begin": 250,
-                "end": 304,
+                "begin": 60,
+                "end": 103,
                 "abridged": false
             },
             "speedometer2": {
@@ -398,11 +399,8 @@
                 "abridged": false
             },
             "rendering.desktop": {
-                "begin": 304,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 30,
+                "begin": 103,
+                "end": 147,
                 "abridged": false
             },
             "speedometer2": {
@@ -415,9 +413,9 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "rendering.desktop.notracing": {
-                "begin": 30,
-                "end": 84,
+            "rendering.desktop": {
+                "begin": 147,
+                "end": 190,
                 "abridged": false
             },
             "speedometer2": {
@@ -430,9 +428,9 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "rendering.desktop.notracing": {
-                "begin": 84,
-                "end": 139,
+            "rendering.desktop": {
+                "begin": 190,
+                "end": 234,
                 "abridged": false
             },
             "speedometer2": {
@@ -445,9 +443,9 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "rendering.desktop.notracing": {
-                "begin": 139,
-                "end": 193,
+            "rendering.desktop": {
+                "begin": 234,
+                "end": 277,
                 "abridged": false
             },
             "speedometer2": {
@@ -460,9 +458,9 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "rendering.desktop.notracing": {
-                "begin": 193,
-                "end": 248,
+            "rendering.desktop": {
+                "begin": 277,
+                "end": 321,
                 "abridged": false
             },
             "speedometer2": {
@@ -475,23 +473,11 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "rendering.desktop.notracing": {
-                "begin": 248,
-                "end": 302,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "23": {
-        "benchmarks": {
-            "blink_perf.shadow_dom": {
+            "rendering.desktop": {
+                "begin": 321,
                 "abridged": false
             },
             "rendering.desktop.notracing": {
-                "begin": 302,
                 "abridged": false
             },
             "speedometer": {
@@ -510,7 +496,22 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 31,
+                "end": 30,
+                "abridged": false
+            }
+        }
+    },
+    "23": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "abridged": false
+            },
+            "system_health.common_desktop": {
+                "begin": 30,
+                "end": 74,
+                "abridged": false
+            },
+            "speedometer2": {
                 "abridged": false
             }
         }
@@ -521,11 +522,11 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "begin": 31,
+                "begin": 74,
                 "abridged": false
             },
             "system_health.memory_desktop": {
-                "end": 4,
+                "end": 36,
                 "abridged": false
             },
             "speedometer2": {
@@ -539,8 +540,8 @@
                 "abridged": false
             },
             "system_health.memory_desktop": {
-                "begin": 4,
-                "end": 59,
+                "begin": 36,
+                "end": 80,
                 "abridged": false
             },
             "speedometer2": {
@@ -554,7 +555,7 @@
                 "abridged": false
             },
             "system_health.memory_desktop": {
-                "begin": 59,
+                "begin": 80,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -567,7 +568,10 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 21,
+                "abridged": false
+            },
+            "v8.browsing_desktop-future": {
+                "end": 2,
                 "abridged": false
             },
             "speedometer2": {
@@ -585,11 +589,8 @@
             "blink_perf.shadow_dom": {
                 "abridged": false
             },
-            "v8.browsing_desktop": {
-                "begin": 21,
-                "abridged": false
-            },
             "v8.browsing_desktop-future": {
+                "begin": 2,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -604,38 +605,38 @@
         }
     },
     "extra_infos": {
-        "num_stories": 2612,
-        "predicted_min_shard_time": 940.0,
+        "num_stories": 2298,
+        "predicted_min_shard_time": 830.0,
         "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 950,
-        "predicted_max_shard_index": 2,
-        "shard #0": 940.0,
-        "shard #1": 940,
-        "shard #2": 950,
-        "shard #3": 940,
-        "shard #4": 950,
-        "shard #5": 940,
-        "shard #6": 950,
-        "shard #7": 943.0,
-        "shard #8": 950,
-        "shard #9": 940,
-        "shard #10": 945.0,
-        "shard #11": 950,
-        "shard #12": 940,
-        "shard #13": 950,
-        "shard #14": 940,
-        "shard #15": 950,
-        "shard #16": 940,
-        "shard #17": 950,
-        "shard #18": 940,
-        "shard #19": 950,
-        "shard #20": 940,
-        "shard #21": 950,
-        "shard #22": 940,
-        "shard #23": 950,
-        "shard #24": 940,
-        "shard #25": 950,
-        "shard #26": 945.0,
-        "shard #27": 940
+        "predicted_max_shard_time": 840,
+        "predicted_max_shard_index": 15,
+        "shard #0": 830.0,
+        "shard #1": 830,
+        "shard #2": 830,
+        "shard #3": 830,
+        "shard #4": 830,
+        "shard #5": 830,
+        "shard #6": 830,
+        "shard #7": 830,
+        "shard #8": 830,
+        "shard #9": 830,
+        "shard #10": 833.0,
+        "shard #11": 830,
+        "shard #12": 830,
+        "shard #13": 835.0,
+        "shard #14": 830,
+        "shard #15": 840,
+        "shard #16": 830,
+        "shard #17": 840,
+        "shard #18": 830,
+        "shard #19": 840,
+        "shard #20": 830,
+        "shard #21": 840,
+        "shard #22": 830,
+        "shard #23": 840,
+        "shard #24": 830,
+        "shard #25": 840,
+        "shard #26": 835.0,
+        "shard #27": 840
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/linux-perf-pgo_map.json b/tools/perf/core/shard_maps/linux-perf-pgo_map.json
index ee94237..07c77eaf 100644
--- a/tools/perf/core/shard_maps/linux-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/linux-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 21,
+                "end": 4,
                 "abridged": false
             }
         },
@@ -22,11 +25,11 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 21,
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 30,
+                "end": 1,
                 "abridged": false
             }
         }
@@ -34,7 +37,16 @@
     "2": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 30,
+                "begin": 1,
+                "end": 49,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 49,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -52,21 +64,25 @@
             }
         }
     },
-    "3": {
+    "4": {
         "benchmarks": {
             "blink_perf.layout": {
                 "begin": 11,
-                "end": 71,
+                "end": 59,
                 "abridged": false
             }
         }
     },
-    "4": {
+    "5": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 71,
+                "begin": 59,
                 "abridged": false
-            },
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
             "blink_perf.owp_storage": {
                 "abridged": false
             },
@@ -74,33 +90,33 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "end": 4,
+                "end": 26,
                 "abridged": false
             }
         }
     },
-    "5": {
+    "7": {
         "benchmarks": {
             "blink_perf.parser": {
-                "begin": 4,
+                "begin": 26,
                 "abridged": false
             },
             "blink_perf.sanitizer-api": {
                 "abridged": false
             },
             "blink_perf.shadow_dom": {
-                "end": 32,
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "end": 4,
                 "abridged": false
             }
         }
     },
-    "6": {
+    "8": {
         "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "begin": 32,
-                "abridged": false
-            },
             "blink_perf.svg": {
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.webaudio": {
@@ -116,15 +132,15 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 4,
+                "end": 1,
                 "abridged": false
             }
         }
     },
-    "7": {
+    "9": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 4,
+                "begin": 1,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -143,7 +159,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 24,
+                "end": 16,
                 "abridged": false
             }
         },
@@ -153,22 +169,31 @@
             }
         }
     },
-    "8": {
+    "10": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 24,
-                "end": 84,
+                "begin": 16,
+                "end": 64,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 84,
+                "begin": 64,
                 "abridged": false
             },
             "media.desktop": {
+                "end": 8,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "media.desktop": {
+                "begin": 8,
                 "abridged": false
             },
             "memory.desktop": {
@@ -176,18 +201,8 @@
             },
             "octane": {
                 "abridged": false
-            }
-        }
-    },
-    "10": {
-        "benchmarks": {
+            },
             "power.desktop": {
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "abridged": false
-            },
-            "rendering.desktop": {
                 "end": 4,
                 "abridged": false
             }
@@ -207,29 +222,17 @@
             }
         }
     },
-    "11": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 4,
-                "end": 64,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 64,
-                "end": 124,
-                "abridged": false
-            }
-        }
-    },
     "13": {
         "benchmarks": {
+            "power.desktop": {
+                "begin": 4,
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 124,
-                "end": 184,
+                "end": 12,
                 "abridged": false
             }
         }
@@ -237,8 +240,8 @@
     "14": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 184,
-                "end": 244,
+                "begin": 12,
+                "end": 60,
                 "abridged": false
             }
         }
@@ -246,8 +249,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 244,
-                "end": 304,
+                "begin": 60,
+                "end": 108,
                 "abridged": false
             }
         }
@@ -255,55 +258,46 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 304,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 35,
+                "begin": 108,
+                "end": 156,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 35,
-                "end": 95,
+            "rendering.desktop": {
+                "begin": 156,
+                "end": 204,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 95,
-                "end": 155,
+            "rendering.desktop": {
+                "begin": 204,
+                "end": 251,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 155,
-                "end": 215,
+            "rendering.desktop": {
+                "begin": 251,
+                "end": 299,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 215,
-                "end": 275,
+            "rendering.desktop": {
+                "begin": 299,
                 "abridged": false
-            }
-        }
-    },
-    "21": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 275,
                 "abridged": false
             },
             "speedometer": {
@@ -322,7 +316,16 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 8,
+                "end": 11,
+                "abridged": false
+            }
+        }
+    },
+    "21": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 11,
+                "end": 59,
                 "abridged": false
             }
         }
@@ -330,20 +333,20 @@
     "22": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 8,
-                "end": 68,
+                "begin": 59,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 25,
                 "abridged": false
             }
         }
     },
     "23": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 68,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 47,
+                "begin": 25,
+                "end": 73,
                 "abridged": false
             }
         }
@@ -351,7 +354,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 47,
+                "begin": 73,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -364,7 +367,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 15,
+                "end": 28,
                 "abridged": false
             }
         },
@@ -377,7 +380,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 15,
+                "begin": 28,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -392,36 +395,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1523,
-        "predicted_min_shard_time": 550,
-        "predicted_min_shard_index": 9,
-        "predicted_max_shard_time": 605.0,
-        "predicted_max_shard_index": 10,
-        "shard #0": 600.0,
-        "shard #1": 600,
-        "shard #2": 600,
-        "shard #3": 600,
-        "shard #4": 600,
-        "shard #5": 600,
-        "shard #6": 600,
-        "shard #7": 603.0,
-        "shard #8": 600,
-        "shard #9": 550,
-        "shard #10": 605.0,
-        "shard #11": 600,
-        "shard #12": 600,
-        "shard #13": 600,
-        "shard #14": 600,
-        "shard #15": 600,
-        "shard #16": 600,
-        "shard #17": 600,
-        "shard #18": 600,
-        "shard #19": 600,
-        "shard #20": 600,
-        "shard #21": 600,
-        "shard #22": 600,
-        "shard #23": 600,
-        "shard #24": 605.0,
-        "shard #25": 600
+        "num_stories": 1209,
+        "predicted_min_shard_time": 470,
+        "predicted_min_shard_index": 18,
+        "predicted_max_shard_time": 480.0,
+        "predicted_max_shard_index": 0,
+        "shard #0": 480.0,
+        "shard #1": 480,
+        "shard #2": 480,
+        "shard #3": 480,
+        "shard #4": 480,
+        "shard #5": 480,
+        "shard #6": 480,
+        "shard #7": 480,
+        "shard #8": 480,
+        "shard #9": 473.0,
+        "shard #10": 480,
+        "shard #11": 480,
+        "shard #12": 475.0,
+        "shard #13": 480,
+        "shard #14": 480,
+        "shard #15": 480,
+        "shard #16": 480,
+        "shard #17": 480,
+        "shard #18": 470,
+        "shard #19": 480,
+        "shard #20": 470,
+        "shard #21": 480,
+        "shard #22": 470,
+        "shard #23": 480,
+        "shard #24": 475.0,
+        "shard #25": 470
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/linux-perf_map.json b/tools/perf/core/shard_maps/linux-perf_map.json
index d4df7be..889a2ca8 100644
--- a/tools/perf/core/shard_maps/linux-perf_map.json
+++ b/tools/perf/core/shard_maps/linux-perf_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 42,
+                "end": 35,
                 "abridged": false
             },
             "jetstream2": {
@@ -45,7 +48,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 42,
+                "begin": 35,
                 "abridged": false
             },
             "blink_perf.css": {
@@ -61,7 +64,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 42,
+                "end": 34,
                 "abridged": false
             },
             "jetstream2": {
@@ -92,7 +95,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 42,
+                "begin": 34,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -102,13 +105,7 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "abridged": false
-            },
-            "blink_perf.sanitizer-api": {
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "end": 11,
+                "end": 29,
                 "abridged": false
             },
             "jetstream2": {
@@ -138,8 +135,14 @@
     },
     "3": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 29,
+                "abridged": false
+            },
+            "blink_perf.sanitizer-api": {
+                "abridged": false
+            },
             "blink_perf.shadow_dom": {
-                "begin": 11,
                 "abridged": false
             },
             "blink_perf.svg": {
@@ -632,15 +635,15 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1253,
+        "num_stories": 1260,
         "predicted_min_shard_time": 990.0,
         "predicted_min_shard_index": 20,
         "predicted_max_shard_time": 1319.0,
         "predicted_max_shard_index": 25,
-        "shard #0": 1072.0,
-        "shard #1": 1070.0,
-        "shard #2": 1034.0,
-        "shard #3": 1061.0,
+        "shard #0": 1075.0,
+        "shard #1": 1072.0,
+        "shard #2": 1074.0,
+        "shard #3": 1086.0,
         "shard #4": 996.0,
         "shard #5": 1096.0,
         "shard #6": 1084.0,
diff --git a/tools/perf/core/shard_maps/mac-laptop_high_end-perf-pgo_map.json b/tools/perf/core/shard_maps/mac-laptop_high_end-perf-pgo_map.json
index 3612e92..cd89c3aa 100644
--- a/tools/perf/core/shard_maps/mac-laptop_high_end-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/mac-laptop_high_end-perf-pgo_map.json
@@ -1,11 +1,11 @@
 {
     "0": {
         "benchmarks": {
-            "blink_perf.accessibility": {
+            "ad_frames.fencedframe": {
                 "abridged": false
             },
-            "blink_perf.bindings": {
-                "end": 13,
+            "blink_perf.accessibility": {
+                "end": 15,
                 "abridged": false
             }
         },
@@ -21,20 +21,32 @@
     },
     "1": {
         "benchmarks": {
-            "blink_perf.bindings": {
-                "begin": 13,
+            "blink_perf.accessibility": {
+                "begin": 15,
                 "abridged": false
             },
-            "blink_perf.css": {
-                "end": 24,
+            "blink_perf.bindings": {
+                "end": 46,
                 "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 46,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 24,
+                "end": 45,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 45,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -47,16 +59,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 7,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 7,
-                "end": 69,
+                "end": 9,
                 "abridged": false
             }
         }
@@ -64,40 +67,58 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 69,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 4,
+                "begin": 9,
+                "end": 58,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 4,
+            "blink_perf.layout": {
+                "begin": 58,
                 "abridged": false
             },
-            "blink_perf.shadow_dom": {
-                "end": 35,
+            "blink_perf.owp_storage": {
+                "end": 1,
                 "abridged": false
             }
         }
     },
     "6": {
         "benchmarks": {
+            "blink_perf.owp_storage": {
+                "begin": 1,
+                "abridged": false
+            },
+            "blink_perf.paint": {
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "end": 28,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 28,
+                "abridged": false
+            },
             "blink_perf.shadow_dom": {
-                "begin": 35,
                 "abridged": false
             },
             "blink_perf.svg": {
+                "end": 9,
+                "abridged": false
+            }
+        }
+    },
+    "8": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 9,
                 "abridged": false
             },
             "blink_perf.webaudio": {
@@ -120,10 +141,10 @@
             }
         }
     },
-    "7": {
+    "9": {
         "benchmarks": {
             "desktop_ui": {
-                "end": 29,
+                "end": 17,
                 "abridged": false
             }
         },
@@ -137,10 +158,10 @@
             }
         }
     },
-    "8": {
+    "10": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 29,
+                "begin": 17,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -159,30 +180,39 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 51,
+                "end": 35,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 51,
+                "begin": 35,
+                "end": 85,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "loading.desktop": {
+                "begin": 85,
                 "abridged": false
             },
             "media.desktop": {
-                "end": 9,
-                "abridged": false
-            }
-        }
-    },
-    "10": {
-        "benchmarks": {
-            "media.desktop": {
-                "begin": 9,
                 "abridged": false
             },
             "memory.desktop": {
+                "end": 6,
+                "abridged": false
+            }
+        }
+    },
+    "13": {
+        "benchmarks": {
+            "memory.desktop": {
+                "begin": 6,
                 "abridged": false
             },
             "octane": {
@@ -192,7 +222,7 @@
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
-                "end": 2,
+                "end": 12,
                 "abridged": false
             }
         },
@@ -211,41 +241,14 @@
             }
         }
     },
-    "11": {
+    "14": {
         "benchmarks": {
             "rasterize_and_record_micro.top_25": {
-                "begin": 2,
+                "begin": 12,
                 "abridged": false
             },
             "rendering.desktop": {
-                "end": 39,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 39,
-                "end": 101,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 101,
-                "end": 163,
-                "abridged": false
-            }
-        }
-    },
-    "14": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 163,
-                "end": 225,
+                "end": 37,
                 "abridged": false
             }
         }
@@ -253,8 +256,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 225,
-                "end": 287,
+                "begin": 37,
+                "end": 87,
                 "abridged": false
             }
         }
@@ -262,55 +265,46 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 287,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 20,
+                "begin": 87,
+                "end": 137,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 20,
-                "end": 82,
+            "rendering.desktop": {
+                "begin": 137,
+                "end": 187,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 82,
-                "end": 144,
+            "rendering.desktop": {
+                "begin": 187,
+                "end": 237,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 144,
-                "end": 206,
+            "rendering.desktop": {
+                "begin": 237,
+                "end": 287,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 206,
-                "end": 268,
+            "rendering.desktop": {
+                "begin": 287,
                 "abridged": false
-            }
-        }
-    },
-    "21": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 268,
                 "abridged": false
             },
             "speedometer": {
@@ -329,7 +323,16 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 3,
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "21": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 1,
+                "end": 51,
                 "abridged": false
             }
         }
@@ -337,20 +340,20 @@
     "22": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 3,
-                "end": 64,
+                "begin": 51,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 19,
                 "abridged": false
             }
         }
     },
     "23": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 64,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 45,
+                "begin": 19,
+                "end": 69,
                 "abridged": false
             }
         }
@@ -358,7 +361,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 45,
+                "begin": 69,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -371,7 +374,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 14,
+                "end": 26,
                 "abridged": false
             }
         }
@@ -379,7 +382,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 14,
+                "begin": 26,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -402,36 +405,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1524,
-        "predicted_min_shard_time": 550,
-        "predicted_min_shard_index": 6,
-        "predicted_max_shard_time": 620.0,
+        "num_stories": 1210,
+        "predicted_min_shard_time": 440,
+        "predicted_min_shard_index": 8,
+        "predicted_max_shard_time": 500.0,
         "predicted_max_shard_index": 0,
-        "shard #0": 620.0,
-        "shard #1": 620,
-        "shard #2": 620,
-        "shard #3": 620,
-        "shard #4": 620,
-        "shard #5": 620,
-        "shard #6": 550,
-        "shard #7": 620.0,
-        "shard #8": 620,
-        "shard #9": 620,
-        "shard #10": 620.0,
-        "shard #11": 620,
-        "shard #12": 620,
-        "shard #13": 620,
-        "shard #14": 620,
-        "shard #15": 620,
-        "shard #16": 620,
-        "shard #17": 620,
-        "shard #18": 620,
-        "shard #19": 620,
-        "shard #20": 620,
-        "shard #21": 620,
-        "shard #22": 610,
-        "shard #23": 620,
-        "shard #24": 610,
-        "shard #25": 617.0
+        "shard #0": 500.0,
+        "shard #1": 500,
+        "shard #2": 500,
+        "shard #3": 500,
+        "shard #4": 490,
+        "shard #5": 500,
+        "shard #6": 490,
+        "shard #7": 500,
+        "shard #8": 440,
+        "shard #9": 500.0,
+        "shard #10": 500,
+        "shard #11": 500,
+        "shard #12": 500,
+        "shard #13": 500.0,
+        "shard #14": 500,
+        "shard #15": 500,
+        "shard #16": 500,
+        "shard #17": 500,
+        "shard #18": 500,
+        "shard #19": 500,
+        "shard #20": 490,
+        "shard #21": 500,
+        "shard #22": 490,
+        "shard #23": 500,
+        "shard #24": 490,
+        "shard #25": 497.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/mac-laptop_high_end-perf_map.json b/tools/perf/core/shard_maps/mac-laptop_high_end-perf_map.json
index e6343be4..8cb69283 100644
--- a/tools/perf/core/shard_maps/mac-laptop_high_end-perf_map.json
+++ b/tools/perf/core/shard_maps/mac-laptop_high_end-perf_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 42,
+                "end": 16,
                 "abridged": false
             },
             "jetstream2": {
@@ -28,7 +31,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 42,
+                "begin": 16,
                 "abridged": false
             },
             "blink_perf.css": {
@@ -44,7 +47,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 67,
+                "end": 12,
                 "abridged": false
             },
             "jetstream2": {
@@ -58,7 +61,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 67,
+                "begin": 12,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -68,10 +71,7 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "end": 13,
+                "end": 5,
                 "abridged": false
             },
             "jetstream2": {
@@ -84,14 +84,32 @@
     },
     "3": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 5,
+                "abridged": false
+            },
             "blink_perf.shadow_dom": {
-                "begin": 13,
                 "abridged": false
             },
             "blink_perf.svg": {
                 "abridged": false
             },
             "blink_perf.webaudio": {
+                "end": 4,
+                "abridged": false
+            },
+            "jetstream2": {
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "4": {
+        "benchmarks": {
+            "blink_perf.webaudio": {
+                "begin": 4,
                 "abridged": false
             },
             "blink_perf.webcodecs": {
@@ -110,7 +128,7 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 1,
+                "end": 5,
                 "abridged": false
             },
             "jetstream2": {
@@ -130,25 +148,10 @@
             }
         }
     },
-    "4": {
-        "benchmarks": {
-            "desktop_ui": {
-                "begin": 1,
-                "end": 29,
-                "abridged": false
-            },
-            "jetstream2": {
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
     "5": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 29,
+                "begin": 5,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -167,7 +170,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 18,
+                "end": 2,
                 "abridged": false
             },
             "speedometer2": {
@@ -178,8 +181,8 @@
     "6": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 18,
-                "end": 44,
+                "begin": 2,
+                "end": 27,
                 "abridged": false
             },
             "speedometer2": {
@@ -190,8 +193,8 @@
     "7": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 44,
-                "end": 70,
+                "begin": 27,
+                "end": 49,
                 "abridged": false
             },
             "speedometer2": {
@@ -202,8 +205,8 @@
     "8": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 70,
-                "end": 95,
+                "begin": 49,
+                "end": 73,
                 "abridged": false
             },
             "speedometer2": {
@@ -214,14 +217,8 @@
     "9": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 95,
-                "abridged": false
-            },
-            "media.desktop": {
-                "abridged": false
-            },
-            "memory.desktop": {
-                "end": 1,
+                "begin": 73,
+                "end": 96,
                 "abridged": false
             },
             "speedometer2": {
@@ -231,15 +228,28 @@
     },
     "10": {
         "benchmarks": {
+            "loading.desktop": {
+                "begin": 96,
+                "abridged": false
+            },
+            "media.desktop": {
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
             "memory.desktop": {
-                "begin": 1,
                 "abridged": false
             },
             "octane": {
                 "abridged": false
             },
             "power.desktop": {
-                "end": 7,
+                "end": 2,
                 "abridged": false
             },
             "speedometer2": {
@@ -261,29 +271,17 @@
             }
         }
     },
-    "11": {
+    "12": {
         "benchmarks": {
             "power.desktop": {
-                "begin": 7,
+                "begin": 2,
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
                 "abridged": false
             },
             "rendering.desktop": {
-                "end": 34,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 34,
-                "end": 98,
+                "end": 18,
                 "abridged": false
             },
             "speedometer2": {
@@ -294,8 +292,8 @@
     "13": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 98,
-                "end": 162,
+                "begin": 18,
+                "end": 75,
                 "abridged": false
             },
             "speedometer2": {
@@ -306,8 +304,8 @@
     "14": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 162,
-                "end": 216,
+                "begin": 75,
+                "end": 137,
                 "abridged": false
             },
             "speedometer2": {
@@ -318,8 +316,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 216,
-                "end": 277,
+                "begin": 137,
+                "end": 187,
                 "abridged": false
             },
             "speedometer2": {
@@ -330,11 +328,8 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 277,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 16,
+                "begin": 187,
+                "end": 242,
                 "abridged": false
             },
             "speedometer2": {
@@ -344,9 +339,9 @@
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 16,
-                "end": 140,
+            "rendering.desktop": {
+                "begin": 242,
+                "end": 287,
                 "abridged": false
             },
             "speedometer2": {
@@ -356,20 +351,11 @@
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 140,
-                "end": 264,
+            "rendering.desktop": {
+                "begin": 287,
                 "abridged": false
             },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "19": {
-        "benchmarks": {
             "rendering.desktop.notracing": {
-                "begin": 264,
                 "abridged": false
             },
             "speedometer": {
@@ -388,7 +374,19 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 19,
+                "end": 7,
+                "abridged": false
+            }
+        }
+    },
+    "19": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 7,
+                "end": 63,
+                "abridged": false
+            },
+            "speedometer2": {
                 "abridged": false
             }
         }
@@ -396,7 +394,11 @@
     "20": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 19,
+                "begin": 63,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 9,
                 "abridged": false
             }
         }
@@ -404,7 +406,8 @@
     "21": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "end": 21,
+                "begin": 9,
+                "end": 28,
                 "abridged": false
             }
         }
@@ -412,8 +415,8 @@
     "22": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 21,
-                "end": 45,
+                "begin": 28,
+                "end": 54,
                 "abridged": false
             }
         }
@@ -421,8 +424,8 @@
     "23": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 45,
-                "end": 72,
+                "begin": 54,
+                "end": 75,
                 "abridged": false
             }
         }
@@ -430,7 +433,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 72,
+                "begin": 75,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -443,7 +446,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 2,
+                "end": 11,
                 "abridged": false
             }
         }
@@ -451,7 +454,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 2,
+                "begin": 11,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -474,36 +477,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1547,
-        "predicted_min_shard_time": 1242.0,
-        "predicted_min_shard_index": 23,
-        "predicted_max_shard_time": 1504.0,
+        "num_stories": 1233,
+        "predicted_min_shard_time": 1111.0,
+        "predicted_min_shard_index": 24,
+        "predicted_max_shard_time": 1274.0,
         "predicted_max_shard_index": 25,
-        "shard #0": 1306.0,
-        "shard #1": 1301.0,
-        "shard #2": 1306.0,
-        "shard #3": 1309.0,
-        "shard #4": 1302.0,
-        "shard #5": 1295.0,
-        "shard #6": 1320.0,
-        "shard #7": 1292.0,
-        "shard #8": 1278.0,
-        "shard #9": 1294.0,
-        "shard #10": 1309.0,
-        "shard #11": 1304.0,
-        "shard #12": 1295.0,
-        "shard #13": 1298.0,
-        "shard #14": 1304.0,
-        "shard #15": 1303.0,
-        "shard #16": 1298.0,
-        "shard #17": 1302.0,
-        "shard #18": 1302.0,
-        "shard #19": 1318.0,
-        "shard #20": 1291.0,
-        "shard #21": 1293.0,
-        "shard #22": 1311.0,
-        "shard #23": 1242.0,
-        "shard #24": 1319.0,
-        "shard #25": 1504.0
+        "shard #0": 1178.0,
+        "shard #1": 1175.0,
+        "shard #2": 1168.0,
+        "shard #3": 1147.0,
+        "shard #4": 1196.0,
+        "shard #5": 1174.0,
+        "shard #6": 1164.0,
+        "shard #7": 1158.0,
+        "shard #8": 1192.0,
+        "shard #9": 1196.0,
+        "shard #10": 1152.0,
+        "shard #11": 1194.0,
+        "shard #12": 1183.0,
+        "shard #13": 1173.0,
+        "shard #14": 1163.0,
+        "shard #15": 1173.0,
+        "shard #16": 1180.0,
+        "shard #17": 1181.0,
+        "shard #18": 1171.0,
+        "shard #19": 1182.0,
+        "shard #20": 1243.0,
+        "shard #21": 1182.0,
+        "shard #22": 1164.0,
+        "shard #23": 1227.0,
+        "shard #24": 1111.0,
+        "shard #25": 1274.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/mac-laptop_low_end-perf-pgo_map.json b/tools/perf/core/shard_maps/mac-laptop_low_end-perf-pgo_map.json
index 16e0b24c..05db244 100644
--- a/tools/perf/core/shard_maps/mac-laptop_low_end-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/mac-laptop_low_end-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 41,
+                "end": 24,
                 "abridged": false
             }
         }
@@ -13,11 +16,11 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 41,
+                "begin": 24,
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 50,
+                "end": 21,
                 "abridged": false
             }
         }
@@ -25,13 +28,22 @@
     "2": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 50,
+                "begin": 21,
                 "abridged": false
             },
             "blink_perf.display_locking": {
                 "abridged": false
             },
             "blink_perf.dom": {
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.dom": {
+                "begin": 1,
                 "abridged": false
             },
             "blink_perf.events": {
@@ -41,16 +53,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 21,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 21,
-                "end": 81,
+                "end": 20,
                 "abridged": false
             }
         }
@@ -58,42 +61,60 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 81,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 14,
+                "begin": 20,
+                "end": 68,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 14,
+            "blink_perf.layout": {
+                "begin": 68,
                 "abridged": false
             },
-            "blink_perf.shadow_dom": {
+            "blink_perf.owp_storage": {
                 "abridged": false
             },
-            "blink_perf.svg": {
-                "end": 4,
+            "blink_perf.paint": {
+                "end": 3,
                 "abridged": false
             }
         }
     },
     "6": {
         "benchmarks": {
-            "blink_perf.svg": {
+            "blink_perf.paint": {
+                "begin": 3,
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 4,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
                 "begin": 4,
                 "abridged": false
             },
+            "blink_perf.svg": {
+                "end": 12,
+                "abridged": false
+            }
+        }
+    },
+    "8": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 12,
+                "abridged": false
+            },
             "blink_perf.webaudio": {
                 "abridged": false
             },
@@ -113,15 +134,15 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 13,
+                "end": 7,
                 "abridged": false
             }
         }
     },
-    "7": {
+    "9": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 13,
+                "begin": 7,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -137,7 +158,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 33,
+                "end": 23,
                 "abridged": false
             }
         },
@@ -147,22 +168,31 @@
             }
         }
     },
-    "8": {
+    "10": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 33,
-                "end": 93,
+                "begin": 23,
+                "end": 71,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 93,
+                "begin": 71,
                 "abridged": false
             },
             "media.desktop": {
+                "end": 14,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "media.desktop": {
+                "begin": 14,
                 "abridged": false
             },
             "memory.desktop": {
@@ -170,6 +200,10 @@
             },
             "octane": {
                 "abridged": false
+            },
+            "power.desktop": {
+                "end": 6,
+                "abridged": false
             }
         },
         "executables": {
@@ -187,43 +221,17 @@
             }
         }
     },
-    "10": {
+    "13": {
         "benchmarks": {
             "power.desktop": {
+                "begin": 6,
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
                 "abridged": false
             },
             "rendering.desktop": {
-                "end": 19,
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 19,
-                "end": 78,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 78,
-                "end": 137,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 137,
-                "end": 196,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -231,8 +239,8 @@
     "14": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 196,
-                "end": 255,
+                "begin": 13,
+                "end": 61,
                 "abridged": false
             }
         }
@@ -240,8 +248,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 255,
-                "end": 314,
+                "begin": 61,
+                "end": 108,
                 "abridged": false
             }
         }
@@ -249,55 +257,46 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 314,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 44,
+                "begin": 108,
+                "end": 156,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 44,
-                "end": 103,
+            "rendering.desktop": {
+                "begin": 156,
+                "end": 203,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 103,
-                "end": 162,
+            "rendering.desktop": {
+                "begin": 203,
+                "end": 251,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 162,
-                "end": 221,
+            "rendering.desktop": {
+                "begin": 251,
+                "end": 298,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 221,
-                "end": 280,
+            "rendering.desktop": {
+                "begin": 298,
                 "abridged": false
-            }
-        }
-    },
-    "21": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 280,
                 "abridged": false
             },
             "speedometer": {
@@ -316,7 +315,16 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 12,
+                "end": 11,
+                "abridged": false
+            }
+        }
+    },
+    "21": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 11,
+                "end": 58,
                 "abridged": false
             }
         }
@@ -324,20 +332,20 @@
     "22": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 12,
-                "end": 71,
+                "begin": 58,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 25,
                 "abridged": false
             }
         }
     },
     "23": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 71,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 49,
+                "begin": 25,
+                "end": 72,
                 "abridged": false
             }
         }
@@ -345,7 +353,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 49,
+                "begin": 72,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -358,7 +366,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 16,
+                "end": 28,
                 "abridged": false
             }
         }
@@ -366,7 +374,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 16,
+                "begin": 28,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -381,36 +389,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1531,
-        "predicted_min_shard_time": 590,
-        "predicted_min_shard_index": 10,
-        "predicted_max_shard_time": 670.0,
-        "predicted_max_shard_index": 9,
-        "shard #0": 600,
-        "shard #1": 600,
-        "shard #2": 600,
-        "shard #3": 600,
-        "shard #4": 600,
-        "shard #5": 600,
-        "shard #6": 600,
-        "shard #7": 593.0,
-        "shard #8": 600,
-        "shard #9": 670.0,
-        "shard #10": 590,
-        "shard #11": 590,
-        "shard #12": 590,
-        "shard #13": 590,
-        "shard #14": 590,
-        "shard #15": 590,
-        "shard #16": 590,
-        "shard #17": 590,
-        "shard #18": 590,
-        "shard #19": 590,
-        "shard #20": 590,
-        "shard #21": 590,
-        "shard #22": 590,
-        "shard #23": 590,
-        "shard #24": 590,
-        "shard #25": 590
+        "num_stories": 1217,
+        "predicted_min_shard_time": 470,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 480,
+        "predicted_max_shard_index": 0,
+        "shard #0": 480,
+        "shard #1": 480,
+        "shard #2": 480,
+        "shard #3": 470,
+        "shard #4": 480,
+        "shard #5": 470,
+        "shard #6": 480,
+        "shard #7": 470,
+        "shard #8": 480,
+        "shard #9": 473.0,
+        "shard #10": 480,
+        "shard #11": 470,
+        "shard #12": 480.0,
+        "shard #13": 470,
+        "shard #14": 480,
+        "shard #15": 470,
+        "shard #16": 480,
+        "shard #17": 470,
+        "shard #18": 480,
+        "shard #19": 470,
+        "shard #20": 480,
+        "shard #21": 470,
+        "shard #22": 480,
+        "shard #23": 470,
+        "shard #24": 480,
+        "shard #25": 470
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/mac-laptop_low_end-perf_map.json b/tools/perf/core/shard_maps/mac-laptop_low_end-perf_map.json
index 76e663b..3cbcef7 100644
--- a/tools/perf/core/shard_maps/mac-laptop_low_end-perf_map.json
+++ b/tools/perf/core/shard_maps/mac-laptop_low_end-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -8,18 +11,18 @@
                 "abridged": false
             },
             "blink_perf.css": {
-                "abridged": false
-            },
-            "blink_perf.display_locking": {
-                "end": 1,
+                "end": 36,
                 "abridged": false
             }
         }
     },
     "1": {
         "benchmarks": {
+            "blink_perf.css": {
+                "begin": 36,
+                "abridged": false
+            },
             "blink_perf.display_locking": {
-                "begin": 1,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -32,21 +35,21 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "end": 9,
+                "end": 88,
                 "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 88,
+                "abridged": false
+            },
+            "blink_perf.owp_storage": {
+                "abridged": false
+            },
             "blink_perf.paint": {
-                "begin": 9,
                 "abridged": false
             },
             "blink_perf.parser": {
@@ -56,18 +59,18 @@
                 "abridged": false
             },
             "blink_perf.svg": {
-                "abridged": false
-            },
-            "blink_perf.webaudio": {
-                "end": 7,
+                "end": 11,
                 "abridged": false
             }
         }
     },
     "3": {
         "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 11,
+                "abridged": false
+            },
             "blink_perf.webaudio": {
-                "begin": 7,
                 "abridged": false
             },
             "blink_perf.webcodecs": {
@@ -86,7 +89,7 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 22,
+                "end": 6,
                 "abridged": false
             }
         }
@@ -94,7 +97,7 @@
     "4": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 22,
+                "begin": 6,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -110,7 +113,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 18,
+                "end": 10,
                 "abridged": false
             }
         },
@@ -123,8 +126,8 @@
     "5": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 18,
-                "end": 42,
+                "begin": 10,
+                "end": 32,
                 "abridged": false
             }
         }
@@ -132,8 +135,8 @@
     "6": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 42,
-                "end": 66,
+                "begin": 32,
+                "end": 53,
                 "abridged": false
             }
         }
@@ -141,8 +144,8 @@
     "7": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 66,
-                "end": 88,
+                "begin": 53,
+                "end": 75,
                 "abridged": false
             }
         }
@@ -150,39 +153,34 @@
     "8": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 88,
-                "abridged": false
-            },
-            "media.desktop": {
-                "end": 11,
+                "begin": 75,
+                "end": 96,
                 "abridged": false
             }
         }
     },
     "9": {
         "benchmarks": {
+            "loading.desktop": {
+                "begin": 96,
+                "abridged": false
+            },
             "media.desktop": {
-                "begin": 11,
-                "abridged": false
-            },
-            "memory.desktop": {
-                "abridged": false
-            },
-            "octane": {
+                "end": 21,
                 "abridged": false
             }
         }
     },
     "10": {
         "benchmarks": {
-            "power.desktop": {
+            "media.desktop": {
+                "begin": 21,
                 "abridged": false
             },
-            "rasterize_and_record_micro.top_25": {
+            "memory.desktop": {
                 "abridged": false
             },
-            "rendering.desktop": {
-                "end": 13,
+            "octane": {
                 "abridged": false
             }
         },
@@ -203,9 +201,14 @@
     },
     "11": {
         "benchmarks": {
+            "power.desktop": {
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 13,
-                "end": 68,
+                "end": 15,
                 "abridged": false
             }
         }
@@ -213,8 +216,8 @@
     "12": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 68,
-                "end": 127,
+                "begin": 15,
+                "end": 66,
                 "abridged": false
             }
         }
@@ -222,8 +225,8 @@
     "13": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 127,
-                "end": 178,
+                "begin": 66,
+                "end": 119,
                 "abridged": false
             }
         }
@@ -231,8 +234,8 @@
     "14": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 178,
-                "end": 236,
+                "begin": 119,
+                "end": 168,
                 "abridged": false
             }
         }
@@ -240,8 +243,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 236,
-                "end": 284,
+                "begin": 168,
+                "end": 213,
                 "abridged": false
             }
         }
@@ -249,28 +252,28 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 284,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 33,
+                "begin": 213,
+                "end": 264,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 33,
-                "end": 184,
+            "rendering.desktop": {
+                "begin": 264,
+                "end": 315,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
+            "rendering.desktop": {
+                "begin": 315,
+                "abridged": false
+            },
             "rendering.desktop.notracing": {
-                "begin": 184,
                 "abridged": false
             },
             "speedometer": {
@@ -278,11 +281,7 @@
             },
             "speedometer-future": {
                 "abridged": false
-            }
-        }
-    },
-    "19": {
-        "benchmarks": {
+            },
             "speedometer2": {
                 "abridged": false
             },
@@ -293,7 +292,16 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 49,
+                "end": 23,
+                "abridged": false
+            }
+        }
+    },
+    "19": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 23,
+                "end": 80,
                 "abridged": false
             }
         }
@@ -301,11 +309,11 @@
     "20": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 49,
+                "begin": 80,
                 "abridged": false
             },
             "system_health.memory_desktop": {
-                "end": 8,
+                "end": 16,
                 "abridged": false
             }
         }
@@ -313,8 +321,8 @@
     "21": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 8,
-                "end": 26,
+                "begin": 16,
+                "end": 36,
                 "abridged": false
             }
         }
@@ -322,8 +330,8 @@
     "22": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 26,
-                "end": 52,
+                "begin": 36,
+                "end": 57,
                 "abridged": false
             }
         }
@@ -331,8 +339,8 @@
     "23": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 52,
-                "end": 74,
+                "begin": 57,
+                "end": 77,
                 "abridged": false
             }
         }
@@ -340,7 +348,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 74,
+                "begin": 77,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -353,7 +361,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 17,
+                "end": 24,
                 "abridged": false
             }
         }
@@ -361,7 +369,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 17,
+                "begin": 24,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -376,36 +384,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1531,
-        "predicted_min_shard_time": 1425.0,
-        "predicted_min_shard_index": 23,
-        "predicted_max_shard_time": 1577.0,
-        "predicted_max_shard_index": 20,
-        "shard #0": 1522.0,
-        "shard #1": 1517.0,
-        "shard #2": 1539.0,
-        "shard #3": 1528.0,
-        "shard #4": 1533.0,
-        "shard #5": 1542.0,
-        "shard #6": 1536.0,
-        "shard #7": 1530.0,
-        "shard #8": 1541.0,
-        "shard #9": 1511.0,
-        "shard #10": 1525.0,
-        "shard #11": 1503.0,
-        "shard #12": 1518.0,
-        "shard #13": 1520.0,
-        "shard #14": 1497.0,
-        "shard #15": 1480.0,
-        "shard #16": 1516.0,
-        "shard #17": 1510,
-        "shard #18": 1475.0,
-        "shard #19": 1509.0,
-        "shard #20": 1577.0,
-        "shard #21": 1488.0,
-        "shard #22": 1536.0,
-        "shard #23": 1425.0,
-        "shard #24": 1550.0,
-        "shard #25": 1549.0
+        "num_stories": 1217,
+        "predicted_min_shard_time": 1353.0,
+        "predicted_min_shard_index": 22,
+        "predicted_max_shard_time": 1431.0,
+        "predicted_max_shard_index": 23,
+        "shard #0": 1387.0,
+        "shard #1": 1391.0,
+        "shard #2": 1388.0,
+        "shard #3": 1406.0,
+        "shard #4": 1405.0,
+        "shard #5": 1402.0,
+        "shard #6": 1380.0,
+        "shard #7": 1372.0,
+        "shard #8": 1414.0,
+        "shard #9": 1389.0,
+        "shard #10": 1415.0,
+        "shard #11": 1383.0,
+        "shard #12": 1380.0,
+        "shard #13": 1378.0,
+        "shard #14": 1372.0,
+        "shard #15": 1390.0,
+        "shard #16": 1398.0,
+        "shard #17": 1396.0,
+        "shard #18": 1397.0,
+        "shard #19": 1416.0,
+        "shard #20": 1357.0,
+        "shard #21": 1365.0,
+        "shard #22": 1353.0,
+        "shard #23": 1431.0,
+        "shard #24": 1372.0,
+        "shard #25": 1370.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf-pgo_map.json b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf-pgo_map.json
index 3612e92..cd89c3aa 100644
--- a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf-pgo_map.json
@@ -1,11 +1,11 @@
 {
     "0": {
         "benchmarks": {
-            "blink_perf.accessibility": {
+            "ad_frames.fencedframe": {
                 "abridged": false
             },
-            "blink_perf.bindings": {
-                "end": 13,
+            "blink_perf.accessibility": {
+                "end": 15,
                 "abridged": false
             }
         },
@@ -21,20 +21,32 @@
     },
     "1": {
         "benchmarks": {
-            "blink_perf.bindings": {
-                "begin": 13,
+            "blink_perf.accessibility": {
+                "begin": 15,
                 "abridged": false
             },
-            "blink_perf.css": {
-                "end": 24,
+            "blink_perf.bindings": {
+                "end": 46,
                 "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 46,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 24,
+                "end": 45,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 45,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -47,16 +59,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 7,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 7,
-                "end": 69,
+                "end": 9,
                 "abridged": false
             }
         }
@@ -64,40 +67,58 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 69,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 4,
+                "begin": 9,
+                "end": 58,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 4,
+            "blink_perf.layout": {
+                "begin": 58,
                 "abridged": false
             },
-            "blink_perf.shadow_dom": {
-                "end": 35,
+            "blink_perf.owp_storage": {
+                "end": 1,
                 "abridged": false
             }
         }
     },
     "6": {
         "benchmarks": {
+            "blink_perf.owp_storage": {
+                "begin": 1,
+                "abridged": false
+            },
+            "blink_perf.paint": {
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "end": 28,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 28,
+                "abridged": false
+            },
             "blink_perf.shadow_dom": {
-                "begin": 35,
                 "abridged": false
             },
             "blink_perf.svg": {
+                "end": 9,
+                "abridged": false
+            }
+        }
+    },
+    "8": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 9,
                 "abridged": false
             },
             "blink_perf.webaudio": {
@@ -120,10 +141,10 @@
             }
         }
     },
-    "7": {
+    "9": {
         "benchmarks": {
             "desktop_ui": {
-                "end": 29,
+                "end": 17,
                 "abridged": false
             }
         },
@@ -137,10 +158,10 @@
             }
         }
     },
-    "8": {
+    "10": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 29,
+                "begin": 17,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -159,30 +180,39 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 51,
+                "end": 35,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 51,
+                "begin": 35,
+                "end": 85,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "loading.desktop": {
+                "begin": 85,
                 "abridged": false
             },
             "media.desktop": {
-                "end": 9,
-                "abridged": false
-            }
-        }
-    },
-    "10": {
-        "benchmarks": {
-            "media.desktop": {
-                "begin": 9,
                 "abridged": false
             },
             "memory.desktop": {
+                "end": 6,
+                "abridged": false
+            }
+        }
+    },
+    "13": {
+        "benchmarks": {
+            "memory.desktop": {
+                "begin": 6,
                 "abridged": false
             },
             "octane": {
@@ -192,7 +222,7 @@
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
-                "end": 2,
+                "end": 12,
                 "abridged": false
             }
         },
@@ -211,41 +241,14 @@
             }
         }
     },
-    "11": {
+    "14": {
         "benchmarks": {
             "rasterize_and_record_micro.top_25": {
-                "begin": 2,
+                "begin": 12,
                 "abridged": false
             },
             "rendering.desktop": {
-                "end": 39,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 39,
-                "end": 101,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 101,
-                "end": 163,
-                "abridged": false
-            }
-        }
-    },
-    "14": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 163,
-                "end": 225,
+                "end": 37,
                 "abridged": false
             }
         }
@@ -253,8 +256,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 225,
-                "end": 287,
+                "begin": 37,
+                "end": 87,
                 "abridged": false
             }
         }
@@ -262,55 +265,46 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 287,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 20,
+                "begin": 87,
+                "end": 137,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 20,
-                "end": 82,
+            "rendering.desktop": {
+                "begin": 137,
+                "end": 187,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 82,
-                "end": 144,
+            "rendering.desktop": {
+                "begin": 187,
+                "end": 237,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 144,
-                "end": 206,
+            "rendering.desktop": {
+                "begin": 237,
+                "end": 287,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 206,
-                "end": 268,
+            "rendering.desktop": {
+                "begin": 287,
                 "abridged": false
-            }
-        }
-    },
-    "21": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 268,
                 "abridged": false
             },
             "speedometer": {
@@ -329,7 +323,16 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 3,
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "21": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 1,
+                "end": 51,
                 "abridged": false
             }
         }
@@ -337,20 +340,20 @@
     "22": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 3,
-                "end": 64,
+                "begin": 51,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 19,
                 "abridged": false
             }
         }
     },
     "23": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 64,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 45,
+                "begin": 19,
+                "end": 69,
                 "abridged": false
             }
         }
@@ -358,7 +361,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 45,
+                "begin": 69,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -371,7 +374,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 14,
+                "end": 26,
                 "abridged": false
             }
         }
@@ -379,7 +382,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 14,
+                "begin": 26,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -402,36 +405,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1524,
-        "predicted_min_shard_time": 550,
-        "predicted_min_shard_index": 6,
-        "predicted_max_shard_time": 620.0,
+        "num_stories": 1210,
+        "predicted_min_shard_time": 440,
+        "predicted_min_shard_index": 8,
+        "predicted_max_shard_time": 500.0,
         "predicted_max_shard_index": 0,
-        "shard #0": 620.0,
-        "shard #1": 620,
-        "shard #2": 620,
-        "shard #3": 620,
-        "shard #4": 620,
-        "shard #5": 620,
-        "shard #6": 550,
-        "shard #7": 620.0,
-        "shard #8": 620,
-        "shard #9": 620,
-        "shard #10": 620.0,
-        "shard #11": 620,
-        "shard #12": 620,
-        "shard #13": 620,
-        "shard #14": 620,
-        "shard #15": 620,
-        "shard #16": 620,
-        "shard #17": 620,
-        "shard #18": 620,
-        "shard #19": 620,
-        "shard #20": 620,
-        "shard #21": 620,
-        "shard #22": 610,
-        "shard #23": 620,
-        "shard #24": 610,
-        "shard #25": 617.0
+        "shard #0": 500.0,
+        "shard #1": 500,
+        "shard #2": 500,
+        "shard #3": 500,
+        "shard #4": 490,
+        "shard #5": 500,
+        "shard #6": 490,
+        "shard #7": 500,
+        "shard #8": 440,
+        "shard #9": 500.0,
+        "shard #10": 500,
+        "shard #11": 500,
+        "shard #12": 500,
+        "shard #13": 500.0,
+        "shard #14": 500,
+        "shard #15": 500,
+        "shard #16": 500,
+        "shard #17": 500,
+        "shard #18": 500,
+        "shard #19": 500,
+        "shard #20": 490,
+        "shard #21": 500,
+        "shard #22": 490,
+        "shard #23": 500,
+        "shard #24": 490,
+        "shard #25": 497.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
index 3d03f56..166e4e2 100644
--- a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
+++ b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
@@ -1,14 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "abridged": false
-            },
-            "blink_perf.css": {
-                "end": 3,
+                "end": 20,
                 "abridged": false
             },
             "jetstream2": {
@@ -30,8 +30,11 @@
     },
     "1": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 20,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "begin": 3,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -44,7 +47,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 89,
+                "end": 28,
                 "abridged": false
             },
             "jetstream2": {
@@ -58,7 +61,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 89,
+                "begin": 28,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -68,19 +71,7 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "abridged": false
-            },
-            "blink_perf.svg": {
-                "abridged": false
-            },
-            "blink_perf.webaudio": {
-                "abridged": false
-            },
-            "blink_perf.webcodecs": {
-                "end": 1,
+                "end": 28,
                 "abridged": false
             },
             "jetstream2": {
@@ -93,8 +84,20 @@
     },
     "3": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 28,
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "abridged": false
+            },
+            "blink_perf.webaudio": {
+                "abridged": false
+            },
             "blink_perf.webcodecs": {
-                "begin": 1,
                 "abridged": false
             },
             "blink_perf.webgl": {
@@ -109,10 +112,6 @@
             "blink_perf.webgpu_fast_call": {
                 "abridged": false
             },
-            "desktop_ui": {
-                "end": 9,
-                "abridged": false
-            },
             "jetstream2": {
                 "abridged": false
             },
@@ -133,7 +132,6 @@
     "4": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 9,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -142,6 +140,16 @@
             "dummy_benchmark.stable_benchmark_1": {
                 "abridged": false
             },
+            "jetstream2": {
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "5": {
+        "benchmarks": {
             "jetstream": {
                 "abridged": false
             },
@@ -152,19 +160,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 11,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "5": {
-        "benchmarks": {
-            "loading.desktop": {
-                "begin": 11,
-                "end": 38,
+                "end": 19,
                 "abridged": false
             },
             "speedometer2": {
@@ -175,8 +171,8 @@
     "6": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 38,
-                "end": 65,
+                "begin": 19,
+                "end": 43,
                 "abridged": false
             },
             "speedometer2": {
@@ -187,8 +183,8 @@
     "7": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 65,
-                "end": 90,
+                "begin": 43,
+                "end": 67,
                 "abridged": false
             },
             "speedometer2": {
@@ -199,11 +195,8 @@
     "8": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 90,
-                "abridged": false
-            },
-            "media.desktop": {
-                "end": 17,
+                "begin": 67,
+                "end": 88,
                 "abridged": false
             },
             "speedometer2": {
@@ -213,18 +206,46 @@
     },
     "9": {
         "benchmarks": {
+            "loading.desktop": {
+                "begin": 88,
+                "abridged": false
+            },
             "media.desktop": {
-                "begin": 17,
+                "end": 11,
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "10": {
+        "benchmarks": {
+            "media.desktop": {
+                "begin": 11,
                 "abridged": false
             },
             "memory.desktop": {
                 "abridged": false
             },
+            "speedometer2": {
+                "abridged": false
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
             "octane": {
                 "abridged": false
             },
             "power.desktop": {
-                "end": 2,
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
+            "rendering.desktop": {
+                "end": 4,
                 "abridged": false
             },
             "speedometer2": {
@@ -246,41 +267,11 @@
             }
         }
     },
-    "10": {
-        "benchmarks": {
-            "power.desktop": {
-                "begin": 2,
-                "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "end": 23,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 23,
-                "end": 89,
-                "abridged": false
-            },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
     "12": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 89,
-                "end": 157,
+                "begin": 4,
+                "end": 57,
                 "abridged": false
             },
             "speedometer2": {
@@ -291,8 +282,8 @@
     "13": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 157,
-                "end": 209,
+                "begin": 57,
+                "end": 117,
                 "abridged": false
             },
             "speedometer2": {
@@ -303,8 +294,8 @@
     "14": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 209,
-                "end": 274,
+                "begin": 117,
+                "end": 173,
                 "abridged": false
             },
             "speedometer2": {
@@ -315,11 +306,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 274,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 20,
+                "begin": 173,
+                "end": 219,
                 "abridged": false
             },
             "speedometer2": {
@@ -329,9 +317,9 @@
     },
     "16": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 20,
-                "end": 128,
+            "rendering.desktop": {
+                "begin": 219,
+                "end": 275,
                 "abridged": false
             },
             "speedometer2": {
@@ -341,20 +329,11 @@
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 128,
-                "end": 237,
+            "rendering.desktop": {
+                "begin": 275,
                 "abridged": false
             },
-            "speedometer2": {
-                "abridged": false
-            }
-        }
-    },
-    "18": {
-        "benchmarks": {
             "rendering.desktop.notracing": {
-                "begin": 237,
                 "abridged": false
             },
             "speedometer": {
@@ -368,12 +347,19 @@
             },
             "speedometer2-future": {
                 "abridged": false
-            },
+            }
+        }
+    },
+    "18": {
+        "benchmarks": {
             "speedometer2-pcscan": {
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 8,
+                "end": 44,
+                "abridged": false
+            },
+            "speedometer2": {
                 "abridged": false
             }
         }
@@ -381,8 +367,11 @@
     "19": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 8,
-                "end": 61,
+                "begin": 44,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 1,
                 "abridged": false
             },
             "speedometer2": {
@@ -392,12 +381,9 @@
     },
     "20": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 61,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 8,
+                "begin": 1,
+                "end": 16,
                 "abridged": false
             }
         }
@@ -405,8 +391,8 @@
     "21": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 8,
-                "end": 24,
+                "begin": 16,
+                "end": 36,
                 "abridged": false
             }
         }
@@ -414,8 +400,8 @@
     "22": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 24,
-                "end": 53,
+                "begin": 36,
+                "end": 60,
                 "abridged": false
             }
         }
@@ -423,8 +409,8 @@
     "23": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 53,
-                "end": 75,
+                "begin": 60,
+                "end": 77,
                 "abridged": false
             }
         }
@@ -432,7 +418,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 75,
+                "begin": 77,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -445,7 +431,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 22,
+                "end": 23,
                 "abridged": false
             }
         }
@@ -453,7 +439,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 22,
+                "begin": 23,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -476,36 +462,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1547,
-        "predicted_min_shard_time": 1081.0,
-        "predicted_min_shard_index": 20,
-        "predicted_max_shard_time": 1182.0,
-        "predicted_max_shard_index": 23,
-        "shard #0": 1115.0,
-        "shard #1": 1116.0,
-        "shard #2": 1118.0,
-        "shard #3": 1107.0,
-        "shard #4": 1104.0,
-        "shard #5": 1135.0,
-        "shard #6": 1123.0,
-        "shard #7": 1133.0,
-        "shard #8": 1117.0,
-        "shard #9": 1114.0,
-        "shard #10": 1111.0,
-        "shard #11": 1117.0,
-        "shard #12": 1113.0,
-        "shard #13": 1112.0,
-        "shard #14": 1124.0,
-        "shard #15": 1119.0,
-        "shard #16": 1113.0,
-        "shard #17": 1123.0,
-        "shard #18": 1143.0,
-        "shard #19": 1118.0,
-        "shard #20": 1081.0,
-        "shard #21": 1107.0,
-        "shard #22": 1104.0,
-        "shard #23": 1182.0,
-        "shard #24": 1096.0,
-        "shard #25": 1108.0
+        "num_stories": 1233,
+        "predicted_min_shard_time": 923.0,
+        "predicted_min_shard_index": 4,
+        "predicted_max_shard_time": 1056.0,
+        "predicted_max_shard_index": 3,
+        "shard #0": 992.0,
+        "shard #1": 995.0,
+        "shard #2": 990.0,
+        "shard #3": 1056.0,
+        "shard #4": 923.0,
+        "shard #5": 992.0,
+        "shard #6": 1009.0,
+        "shard #7": 1001.0,
+        "shard #8": 977.0,
+        "shard #9": 1033.0,
+        "shard #10": 975.0,
+        "shard #11": 996.0,
+        "shard #12": 991.0,
+        "shard #13": 984.0,
+        "shard #14": 984.0,
+        "shard #15": 989.0,
+        "shard #16": 983.0,
+        "shard #17": 977.0,
+        "shard #18": 987.0,
+        "shard #19": 1006.0,
+        "shard #20": 990.0,
+        "shard #21": 984.0,
+        "shard #22": 981.0,
+        "shard #23": 1011.0,
+        "shard #24": 1013.0,
+        "shard #25": 1053.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/win-10-perf-pgo_map.json b/tools/perf/core/shard_maps/win-10-perf-pgo_map.json
index 5f51994..d4f2aa17 100644
--- a/tools/perf/core/shard_maps/win-10-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/win-10-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 23,
+                "end": 6,
                 "abridged": false
             }
         },
@@ -22,11 +25,11 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 23,
+                "begin": 6,
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 34,
+                "end": 5,
                 "abridged": false
             }
         }
@@ -34,7 +37,16 @@
     "2": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 34,
+                "begin": 5,
+                "end": 55,
+                "abridged": false
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 55,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -47,16 +59,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 17,
-                "abridged": false
-            }
-        }
-    },
-    "3": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 17,
-                "end": 79,
+                "end": 19,
                 "abridged": false
             }
         }
@@ -64,40 +67,58 @@
     "4": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 79,
-                "abridged": false
-            },
-            "blink_perf.owp_storage": {
-                "abridged": false
-            },
-            "blink_perf.paint": {
-                "abridged": false
-            },
-            "blink_perf.parser": {
-                "end": 14,
+                "begin": 19,
+                "end": 69,
                 "abridged": false
             }
         }
     },
     "5": {
         "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 14,
+            "blink_perf.layout": {
+                "begin": 69,
                 "abridged": false
             },
-            "blink_perf.shadow_dom": {
+            "blink_perf.owp_storage": {
                 "abridged": false
             },
-            "blink_perf.svg": {
-                "end": 6,
+            "blink_perf.paint": {
+                "end": 7,
                 "abridged": false
             }
         }
     },
     "6": {
         "benchmarks": {
+            "blink_perf.paint": {
+                "begin": 7,
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 10,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "begin": 10,
+                "abridged": false
+            },
             "blink_perf.svg": {
-                "begin": 6,
+                "end": 21,
+                "abridged": false
+            }
+        }
+    },
+    "8": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 21,
                 "abridged": false
             },
             "blink_perf.webaudio": {
@@ -128,13 +149,7 @@
             }
         }
     },
-    "7": {
-        "benchmarks": {
-            "desktop_ui": {
-                "end": 2,
-                "abridged": false
-            }
-        },
+    "9": {
         "executables": {
             "dawn_perf_tests": {
                 "arguments": [
@@ -143,12 +158,12 @@
                 ],
                 "path": "dawn_perf_tests"
             }
-        }
+        },
+        "benchmarks": {}
     },
-    "8": {
+    "10": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 2,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -167,27 +182,36 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 24,
+                "end": 18,
                 "abridged": false
             }
         }
     },
-    "9": {
+    "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 24,
-                "end": 86,
+                "begin": 18,
+                "end": 68,
                 "abridged": false
             }
         }
     },
-    "10": {
+    "12": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 86,
+                "begin": 68,
                 "abridged": false
             },
             "media.desktop": {
+                "end": 14,
+                "abridged": false
+            }
+        }
+    },
+    "13": {
+        "benchmarks": {
+            "media.desktop": {
+                "begin": 14,
                 "abridged": false
             },
             "memory.desktop": {
@@ -197,49 +221,22 @@
                 "abridged": false
             },
             "power.desktop": {
-                "end": 9,
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "power.desktop": {
-                "begin": 9,
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "end": 31,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 31,
-                "end": 93,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 93,
-                "end": 155,
+                "end": 14,
                 "abridged": false
             }
         }
     },
     "14": {
         "benchmarks": {
+            "rasterize_and_record_micro.top_25": {
+                "begin": 14,
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 155,
-                "end": 217,
+                "end": 39,
                 "abridged": false
             }
         }
@@ -247,8 +244,8 @@
     "15": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 217,
-                "end": 280,
+                "begin": 39,
+                "end": 89,
                 "abridged": false
             }
         }
@@ -256,55 +253,46 @@
     "16": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 280,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 13,
+                "begin": 89,
+                "end": 138,
                 "abridged": false
             }
         }
     },
     "17": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 13,
-                "end": 76,
+            "rendering.desktop": {
+                "begin": 138,
+                "end": 188,
                 "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 76,
-                "end": 138,
+            "rendering.desktop": {
+                "begin": 188,
+                "end": 237,
                 "abridged": false
             }
         }
     },
     "19": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 138,
-                "end": 201,
+            "rendering.desktop": {
+                "begin": 237,
+                "end": 287,
                 "abridged": false
             }
         }
     },
     "20": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 201,
-                "end": 263,
+            "rendering.desktop": {
+                "begin": 287,
                 "abridged": false
-            }
-        }
-    },
-    "21": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 263,
                 "abridged": false
             },
             "speedometer": {
@@ -318,28 +306,42 @@
             },
             "speedometer2-future": {
                 "abridged": false
+            },
+            "speedometer2-pcscan": {
+                "abridged": false
+            },
+            "system_health.common_desktop": {
+                "end": 1,
+                "abridged": false
+            }
+        }
+    },
+    "21": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 1,
+                "end": 51,
+                "abridged": false
             }
         }
     },
     "22": {
         "benchmarks": {
-            "speedometer2-pcscan": {
+            "system_health.common_desktop": {
+                "begin": 51,
                 "abridged": false
             },
-            "system_health.common_desktop": {
-                "end": 61,
+            "system_health.memory_desktop": {
+                "end": 19,
                 "abridged": false
             }
         }
     },
     "23": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 61,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 43,
+                "begin": 19,
+                "end": 69,
                 "abridged": false
             }
         }
@@ -347,7 +349,7 @@
     "24": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 43,
+                "begin": 69,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -360,7 +362,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 13,
+                "end": 26,
                 "abridged": false
             }
         }
@@ -368,7 +370,7 @@
     "25": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 13,
+                "begin": 26,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -391,36 +393,36 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1524,
-        "predicted_min_shard_time": 575.0,
-        "predicted_min_shard_index": 6,
-        "predicted_max_shard_time": 630,
-        "predicted_max_shard_index": 15,
-        "shard #0": 620.0,
-        "shard #1": 620,
-        "shard #2": 620,
-        "shard #3": 620,
-        "shard #4": 620,
-        "shard #5": 620,
-        "shard #6": 575.0,
-        "shard #7": 620.0,
-        "shard #8": 620,
-        "shard #9": 620,
-        "shard #10": 620,
-        "shard #11": 620,
-        "shard #12": 620,
-        "shard #13": 620,
-        "shard #14": 620,
-        "shard #15": 630,
-        "shard #16": 620,
-        "shard #17": 630,
-        "shard #18": 620,
-        "shard #19": 630,
-        "shard #20": 620,
-        "shard #21": 630,
-        "shard #22": 620,
-        "shard #23": 630,
-        "shard #24": 620,
-        "shard #25": 627.0
+        "num_stories": 1210,
+        "predicted_min_shard_time": 445.0,
+        "predicted_min_shard_index": 8,
+        "predicted_max_shard_time": 600.0,
+        "predicted_max_shard_index": 9,
+        "shard #0": 500.0,
+        "shard #1": 500,
+        "shard #2": 500,
+        "shard #3": 500,
+        "shard #4": 500,
+        "shard #5": 500,
+        "shard #6": 500,
+        "shard #7": 500,
+        "shard #8": 445.0,
+        "shard #9": 600.0,
+        "shard #10": 500,
+        "shard #11": 500,
+        "shard #12": 500,
+        "shard #13": 500,
+        "shard #14": 500,
+        "shard #15": 500,
+        "shard #16": 490,
+        "shard #17": 500,
+        "shard #18": 490,
+        "shard #19": 500,
+        "shard #20": 490,
+        "shard #21": 500,
+        "shard #22": 490,
+        "shard #23": 500,
+        "shard #24": 490,
+        "shard #25": 497.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/win-10-perf_map.json b/tools/perf/core/shard_maps/win-10-perf_map.json
index 03a827aa..6c74d6f7 100644
--- a/tools/perf/core/shard_maps/win-10-perf_map.json
+++ b/tools/perf/core/shard_maps/win-10-perf_map.json
@@ -1,6 +1,9 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
@@ -8,7 +11,7 @@
                 "abridged": false
             },
             "blink_perf.css": {
-                "end": 17,
+                "end": 11,
                 "abridged": false
             },
             "jetstream2": {
@@ -44,7 +47,7 @@
     "1": {
         "benchmarks": {
             "blink_perf.css": {
-                "begin": 17,
+                "begin": 11,
                 "abridged": false
             },
             "blink_perf.dom": {
@@ -57,7 +60,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 95,
+                "end": 86,
                 "abridged": false
             },
             "jetstream2": {
@@ -84,7 +87,7 @@
     "2": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 95,
+                "begin": 86,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -103,7 +106,7 @@
                 "abridged": false
             },
             "blink_perf.webaudio": {
-                "end": 6,
+                "end": 3,
                 "abridged": false
             },
             "jetstream2": {
@@ -130,7 +133,7 @@
     "3": {
         "benchmarks": {
             "blink_perf.webaudio": {
-                "begin": 6,
+                "begin": 3,
                 "abridged": false
             },
             "blink_perf.webcodecs": {
@@ -206,6 +209,13 @@
             "jetstream2": {
                 "abridged": false
             },
+            "kraken": {
+                "abridged": false
+            },
+            "loading.desktop": {
+                "end": 4,
+                "abridged": false
+            },
             "system_health.common_desktop": {
                 "sections": [
                     {
@@ -226,14 +236,9 @@
     },
     "5": {
         "benchmarks": {
-            "jetstream2": {
-                "abridged": false
-            },
-            "kraken": {
-                "abridged": false
-            },
             "loading.desktop": {
-                "end": 29,
+                "begin": 4,
+                "end": 38,
                 "abridged": false
             },
             "system_health.common_desktop": {
@@ -257,8 +262,8 @@
     "6": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 29,
-                "end": 66,
+                "begin": 38,
+                "end": 73,
                 "abridged": false
             },
             "system_health.common_desktop": {
@@ -282,8 +287,11 @@
     "7": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 66,
-                "end": 98,
+                "begin": 73,
+                "abridged": false
+            },
+            "media.desktop": {
+                "end": 1,
                 "abridged": false
             },
             "system_health.common_desktop": {
@@ -306,15 +314,12 @@
     },
     "8": {
         "benchmarks": {
-            "loading.desktop": {
-                "begin": 98,
-                "abridged": false
-            },
             "media.desktop": {
+                "begin": 1,
                 "abridged": false
             },
             "memory.desktop": {
-                "end": 4,
+                "end": 6,
                 "abridged": false
             },
             "system_health.common_desktop": {
@@ -338,7 +343,7 @@
     "9": {
         "benchmarks": {
             "memory.desktop": {
-                "begin": 4,
+                "begin": 6,
                 "abridged": false
             },
             "octane": {
@@ -348,7 +353,10 @@
                 "abridged": false
             },
             "rasterize_and_record_micro.top_25": {
-                "end": 16,
+                "abridged": false
+            },
+            "rendering.desktop": {
+                "end": 8,
                 "abridged": false
             },
             "system_health.common_desktop": {
@@ -371,12 +379,9 @@
     },
     "10": {
         "benchmarks": {
-            "rasterize_and_record_micro.top_25": {
-                "begin": 16,
-                "abridged": false
-            },
             "rendering.desktop": {
-                "end": 83,
+                "begin": 8,
+                "end": 104,
                 "abridged": false
             },
             "speedometer2": {
@@ -387,8 +392,8 @@
     "11": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 83,
-                "end": 190,
+                "begin": 104,
+                "end": 198,
                 "abridged": false
             },
             "speedometer2": {
@@ -399,8 +404,8 @@
     "12": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 190,
-                "end": 265,
+                "begin": 198,
+                "end": 276,
                 "abridged": false
             },
             "speedometer2": {
@@ -411,7 +416,7 @@
     "13": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 265,
+                "begin": 276,
                 "abridged": false
             },
             "rendering.desktop.notracing": {
@@ -433,7 +438,7 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 1,
+                "end": 9,
                 "abridged": false
             }
         }
@@ -441,8 +446,8 @@
     "14": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 1,
-                "end": 73,
+                "begin": 9,
+                "end": 80,
                 "abridged": false
             },
             "speedometer2": {
@@ -453,11 +458,11 @@
     "15": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 73,
+                "begin": 80,
                 "abridged": false
             },
             "system_health.memory_desktop": {
-                "end": 21,
+                "end": 23,
                 "abridged": false
             },
             "speedometer2": {
@@ -468,8 +473,8 @@
     "16": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 21,
-                "end": 55,
+                "begin": 23,
+                "end": 61,
                 "abridged": false
             },
             "speedometer2": {
@@ -480,8 +485,8 @@
     "17": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 55,
-                "end": 74,
+                "begin": 61,
+                "end": 76,
                 "abridged": false
             },
             "speedometer2": {
@@ -492,7 +497,7 @@
     "18": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 74,
+                "begin": 76,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -505,7 +510,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 23,
+                "end": 26,
                 "abridged": false
             },
             "speedometer2": {
@@ -516,7 +521,7 @@
     "19": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 23,
+                "begin": 26,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
@@ -542,30 +547,30 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1251,
-        "predicted_min_shard_time": 1869.0,
-        "predicted_min_shard_index": 3,
-        "predicted_max_shard_time": 2234.0,
+        "num_stories": 1260,
+        "predicted_min_shard_time": 1886.0,
+        "predicted_min_shard_index": 17,
+        "predicted_max_shard_time": 2041.0,
         "predicted_max_shard_index": 19,
-        "shard #0": 1952.0,
-        "shard #1": 1955.0,
-        "shard #2": 1944.0,
-        "shard #3": 1869.0,
-        "shard #4": 1913.0,
-        "shard #5": 1973.0,
-        "shard #6": 1971.0,
-        "shard #7": 1965.0,
-        "shard #8": 1959.0,
-        "shard #9": 1951.0,
-        "shard #10": 1962.0,
-        "shard #11": 1963.0,
-        "shard #12": 1952.0,
-        "shard #13": 1950.0,
-        "shard #14": 1959.0,
-        "shard #15": 1891.0,
-        "shard #16": 1991.0,
-        "shard #17": 2024.0,
-        "shard #18": 1961.0,
-        "shard #19": 2234.0
+        "shard #0": 1942.0,
+        "shard #1": 1950.0,
+        "shard #2": 1934.0,
+        "shard #3": 1984.0,
+        "shard #4": 1960.0,
+        "shard #5": 1961.0,
+        "shard #6": 1957.0,
+        "shard #7": 1941.0,
+        "shard #8": 1933.0,
+        "shard #9": 1933.0,
+        "shard #10": 1946.0,
+        "shard #11": 1930.0,
+        "shard #12": 1944.0,
+        "shard #13": 1976.0,
+        "shard #14": 1949.0,
+        "shard #15": 1935.0,
+        "shard #16": 1958.0,
+        "shard #17": 1886.0,
+        "shard #18": 1980.0,
+        "shard #19": 2041.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/win-10_laptop_low_end-perf-pgo_map.json b/tools/perf/core/shard_maps/win-10_laptop_low_end-perf-pgo_map.json
index dcd4de04..8bfc70f 100644
--- a/tools/perf/core/shard_maps/win-10_laptop_low_end-perf-pgo_map.json
+++ b/tools/perf/core/shard_maps/win-10_laptop_low_end-perf-pgo_map.json
@@ -1,11 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
-                "end": 22,
+                "end": 9,
                 "abridged": false
             }
         }
@@ -13,31 +16,40 @@
     "1": {
         "benchmarks": {
             "blink_perf.bindings": {
-                "begin": 22,
-                "abridged": false
-            },
-            "blink_perf.css": {
-                "end": 12,
+                "begin": 9,
+                "end": 42,
                 "abridged": false
             }
         }
     },
     "2": {
         "benchmarks": {
-            "blink_perf.css": {
-                "begin": 12,
+            "blink_perf.bindings": {
+                "begin": 42,
                 "abridged": false
             },
-            "blink_perf.dom": {
-                "end": 1,
+            "blink_perf.css": {
+                "end": 24,
                 "abridged": false
             }
         }
     },
     "3": {
         "benchmarks": {
+            "blink_perf.css": {
+                "begin": 24,
+                "end": 57,
+                "abridged": false
+            }
+        }
+    },
+    "4": {
+        "benchmarks": {
+            "blink_perf.css": {
+                "begin": 57,
+                "abridged": false
+            },
             "blink_perf.dom": {
-                "begin": 1,
                 "abridged": false
             },
             "blink_perf.events": {
@@ -47,16 +59,7 @@
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 15,
-                "abridged": false
-            }
-        }
-    },
-    "4": {
-        "benchmarks": {
-            "blink_perf.layout": {
-                "begin": 15,
-                "end": 56,
+                "end": 4,
                 "abridged": false
             }
         }
@@ -64,8 +67,8 @@
     "5": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 56,
-                "end": 97,
+                "begin": 4,
+                "end": 37,
                 "abridged": false
             }
         }
@@ -73,7 +76,25 @@
     "6": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 97,
+                "begin": 37,
+                "end": 70,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 70,
+                "end": 103,
+                "abridged": false
+            }
+        }
+    },
+    "8": {
+        "benchmarks": {
+            "blink_perf.layout": {
+                "begin": 103,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -83,45 +104,54 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "end": 11,
-                "abridged": false
-            }
-        }
-    },
-    "7": {
-        "benchmarks": {
-            "blink_perf.parser": {
-                "begin": 11,
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "end": 21,
-                "abridged": false
-            }
-        }
-    },
-    "8": {
-        "benchmarks": {
-            "blink_perf.shadow_dom": {
-                "begin": 21,
-                "abridged": false
-            },
-            "blink_perf.svg": {
-                "end": 23,
+                "end": 7,
                 "abridged": false
             }
         }
     },
     "9": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 7,
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 10,
+                "abridged": false
+            }
+        }
+    },
+    "10": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "begin": 10,
+                "abridged": false
+            },
             "blink_perf.svg": {
-                "begin": 23,
+                "end": 5,
+                "abridged": false
+            }
+        }
+    },
+    "11": {
+        "benchmarks": {
+            "blink_perf.svg": {
+                "begin": 5,
                 "abridged": false
             },
             "blink_perf.webaudio": {
                 "abridged": false
             },
             "blink_perf.webcodecs": {
+                "end": 5,
+                "abridged": false
+            }
+        }
+    },
+    "12": {
+        "benchmarks": {
+            "blink_perf.webcodecs": {
+                "begin": 5,
                 "abridged": false
             },
             "blink_perf.webgl": {
@@ -137,15 +167,15 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 13,
+                "end": 19,
                 "abridged": false
             }
         }
     },
-    "10": {
+    "13": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 13,
+                "begin": 19,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -164,48 +194,48 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 14,
-                "abridged": false
-            }
-        }
-    },
-    "11": {
-        "benchmarks": {
-            "loading.desktop": {
-                "begin": 14,
-                "end": 55,
-                "abridged": false
-            }
-        }
-    },
-    "12": {
-        "benchmarks": {
-            "loading.desktop": {
-                "begin": 55,
-                "end": 96,
-                "abridged": false
-            }
-        }
-    },
-    "13": {
-        "benchmarks": {
-            "loading.desktop": {
-                "begin": 96,
-                "abridged": false
-            },
-            "media.desktop": {
-                "abridged": false
-            },
-            "memory.desktop": {
-                "end": 8,
+                "end": 20,
                 "abridged": false
             }
         }
     },
     "14": {
         "benchmarks": {
+            "loading.desktop": {
+                "begin": 20,
+                "end": 54,
+                "abridged": false
+            }
+        }
+    },
+    "15": {
+        "benchmarks": {
+            "loading.desktop": {
+                "begin": 54,
+                "end": 87,
+                "abridged": false
+            }
+        }
+    },
+    "16": {
+        "benchmarks": {
+            "loading.desktop": {
+                "begin": 87,
+                "abridged": false
+            },
+            "media.desktop": {
+                "end": 17,
+                "abridged": false
+            }
+        }
+    },
+    "17": {
+        "benchmarks": {
+            "media.desktop": {
+                "begin": 17,
+                "abridged": false
+            },
             "memory.desktop": {
-                "begin": 8,
                 "abridged": false
             },
             "octane": {
@@ -213,48 +243,16 @@
             },
             "power.desktop": {
                 "abridged": false
-            },
-            "rasterize_and_record_micro.top_25": {
-                "end": 24,
-                "abridged": false
-            }
-        }
-    },
-    "15": {
-        "benchmarks": {
-            "rasterize_and_record_micro.top_25": {
-                "begin": 24,
-                "abridged": false
-            },
-            "rendering.desktop": {
-                "end": 40,
-                "abridged": false
-            }
-        }
-    },
-    "16": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 40,
-                "end": 81,
-                "abridged": false
-            }
-        }
-    },
-    "17": {
-        "benchmarks": {
-            "rendering.desktop": {
-                "begin": 81,
-                "end": 122,
-                "abridged": false
             }
         }
     },
     "18": {
         "benchmarks": {
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 122,
-                "end": 163,
+                "end": 9,
                 "abridged": false
             }
         }
@@ -262,8 +260,8 @@
     "19": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 163,
-                "end": 204,
+                "begin": 9,
+                "end": 42,
                 "abridged": false
             }
         }
@@ -271,8 +269,8 @@
     "20": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 204,
-                "end": 245,
+                "begin": 42,
+                "end": 76,
                 "abridged": false
             }
         }
@@ -280,8 +278,8 @@
     "21": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 245,
-                "end": 286,
+                "begin": 76,
+                "end": 109,
                 "abridged": false
             }
         }
@@ -289,8 +287,8 @@
     "22": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 286,
-                "end": 328,
+                "begin": 109,
+                "end": 143,
                 "abridged": false
             }
         }
@@ -298,73 +296,55 @@
     "23": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 328,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 40,
+                "begin": 143,
+                "end": 176,
                 "abridged": false
             }
         }
     },
     "24": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 40,
-                "end": 82,
+            "rendering.desktop": {
+                "begin": 176,
+                "end": 210,
                 "abridged": false
             }
         }
     },
     "25": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 82,
-                "end": 123,
+            "rendering.desktop": {
+                "begin": 210,
+                "end": 243,
                 "abridged": false
             }
         }
     },
     "26": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 123,
-                "end": 165,
+            "rendering.desktop": {
+                "begin": 243,
+                "end": 277,
                 "abridged": false
             }
         }
     },
     "27": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 165,
-                "end": 206,
+            "rendering.desktop": {
+                "begin": 277,
+                "end": 310,
                 "abridged": false
             }
         }
     },
     "28": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 206,
-                "end": 248,
+            "rendering.desktop": {
+                "begin": 310,
                 "abridged": false
-            }
-        }
-    },
-    "29": {
-        "benchmarks": {
+            },
             "rendering.desktop.notracing": {
-                "begin": 248,
-                "end": 289,
-                "abridged": false
-            }
-        }
-    },
-    "30": {
-        "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 289,
                 "abridged": false
             },
             "speedometer": {
@@ -383,7 +363,25 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 4,
+                "end": 9,
+                "abridged": false
+            }
+        }
+    },
+    "29": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 9,
+                "end": 42,
+                "abridged": false
+            }
+        }
+    },
+    "30": {
+        "benchmarks": {
+            "system_health.common_desktop": {
+                "begin": 42,
+                "end": 76,
                 "abridged": false
             }
         }
@@ -391,20 +389,20 @@
     "31": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 4,
-                "end": 45,
+                "begin": 76,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 28,
                 "abridged": false
             }
         }
     },
     "32": {
         "benchmarks": {
-            "system_health.common_desktop": {
-                "begin": 45,
-                "abridged": false
-            },
             "system_health.memory_desktop": {
-                "end": 6,
+                "begin": 28,
+                "end": 62,
                 "abridged": false
             }
         }
@@ -412,16 +410,7 @@
     "33": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 6,
-                "end": 47,
-                "abridged": false
-            }
-        }
-    },
-    "34": {
-        "benchmarks": {
-            "system_health.memory_desktop": {
-                "begin": 47,
+                "begin": 62,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -431,34 +420,43 @@
                 "abridged": false
             },
             "tracing.tracing_with_background_memory_infra": {
-                "end": 6,
+                "abridged": false
+            },
+            "v8.browsing_desktop": {
+                "end": 3,
+                "abridged": false
+            }
+        }
+    },
+    "34": {
+        "benchmarks": {
+            "v8.browsing_desktop": {
+                "begin": 3,
+                "abridged": false
+            },
+            "v8.browsing_desktop-future": {
+                "end": 8,
                 "abridged": false
             }
         }
     },
     "35": {
         "benchmarks": {
-            "tracing.tracing_with_background_memory_infra": {
-                "begin": 6,
-                "abridged": false
-            },
-            "v8.browsing_desktop": {
-                "abridged": false
-            },
             "v8.browsing_desktop-future": {
-                "end": 9,
+                "begin": 8,
+                "abridged": false
+            },
+            "v8.runtime_stats.top_25": {
+                "end": 12,
                 "abridged": false
             }
         }
     },
     "36": {
         "benchmarks": {
-            "v8.browsing_desktop-future": {
-                "begin": 9,
-                "abridged": false
-            },
             "v8.runtime_stats.top_25": {
-                "end": 22,
+                "begin": 12,
+                "end": 46,
                 "abridged": false
             }
         }
@@ -466,8 +464,8 @@
     "37": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 22,
-                "end": 63,
+                "begin": 46,
+                "end": 79,
                 "abridged": false
             }
         }
@@ -475,8 +473,8 @@
     "38": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 63,
-                "end": 105,
+                "begin": 79,
+                "end": 113,
                 "abridged": false
             }
         }
@@ -484,7 +482,7 @@
     "39": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 105,
+                "begin": 113,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -496,50 +494,50 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1649,
-        "predicted_min_shard_time": 410,
+        "num_stories": 1335,
+        "predicted_min_shard_time": 330,
         "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 420,
-        "predicted_max_shard_index": 22,
-        "shard #0": 410,
-        "shard #1": 410,
-        "shard #2": 410,
-        "shard #3": 410,
-        "shard #4": 410,
-        "shard #5": 410,
-        "shard #6": 410,
-        "shard #7": 410,
-        "shard #8": 410,
-        "shard #9": 410,
-        "shard #10": 410,
-        "shard #11": 410,
-        "shard #12": 410,
-        "shard #13": 410,
-        "shard #14": 410,
-        "shard #15": 410,
-        "shard #16": 410,
-        "shard #17": 410,
-        "shard #18": 410,
-        "shard #19": 410,
-        "shard #20": 410,
-        "shard #21": 410,
-        "shard #22": 420,
-        "shard #23": 410,
-        "shard #24": 420,
-        "shard #25": 410,
-        "shard #26": 420,
-        "shard #27": 410,
-        "shard #28": 420,
-        "shard #29": 410,
-        "shard #30": 420,
-        "shard #31": 410,
-        "shard #32": 420,
-        "shard #33": 410,
-        "shard #34": 420,
-        "shard #35": 410,
-        "shard #36": 420,
-        "shard #37": 410,
-        "shard #38": 420,
-        "shard #39": 410
+        "predicted_max_shard_time": 340,
+        "predicted_max_shard_index": 10,
+        "shard #0": 330,
+        "shard #1": 330,
+        "shard #2": 330,
+        "shard #3": 330,
+        "shard #4": 330,
+        "shard #5": 330,
+        "shard #6": 330,
+        "shard #7": 330,
+        "shard #8": 330,
+        "shard #9": 330,
+        "shard #10": 340,
+        "shard #11": 330,
+        "shard #12": 340,
+        "shard #13": 330,
+        "shard #14": 340,
+        "shard #15": 330,
+        "shard #16": 340,
+        "shard #17": 330,
+        "shard #18": 340,
+        "shard #19": 330,
+        "shard #20": 340,
+        "shard #21": 330,
+        "shard #22": 340,
+        "shard #23": 330,
+        "shard #24": 340,
+        "shard #25": 330,
+        "shard #26": 340,
+        "shard #27": 330,
+        "shard #28": 340,
+        "shard #29": 330,
+        "shard #30": 340,
+        "shard #31": 330,
+        "shard #32": 340,
+        "shard #33": 330,
+        "shard #34": 340,
+        "shard #35": 330,
+        "shard #36": 340,
+        "shard #37": 330,
+        "shard #38": 340,
+        "shard #39": 330
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/win-10_laptop_low_end-perf_map.json b/tools/perf/core/shard_maps/win-10_laptop_low_end-perf_map.json
index 716f856..8e2691d7 100644
--- a/tools/perf/core/shard_maps/win-10_laptop_low_end-perf_map.json
+++ b/tools/perf/core/shard_maps/win-10_laptop_low_end-perf_map.json
@@ -1,10 +1,14 @@
 {
     "0": {
         "benchmarks": {
+            "ad_frames.fencedframe": {
+                "abridged": false
+            },
             "blink_perf.accessibility": {
                 "abridged": false
             },
             "blink_perf.bindings": {
+                "end": 46,
                 "abridged": false
             },
             "jetstream2": {
@@ -17,14 +21,12 @@
     },
     "1": {
         "benchmarks": {
+            "blink_perf.bindings": {
+                "begin": 46,
+                "abridged": false
+            },
             "blink_perf.css": {
-                "abridged": false
-            },
-            "blink_perf.dom": {
-                "abridged": false
-            },
-            "blink_perf.events": {
-                "end": 1,
+                "end": 56,
                 "abridged": false
             },
             "jetstream2": {
@@ -37,15 +39,21 @@
     },
     "2": {
         "benchmarks": {
+            "blink_perf.css": {
+                "begin": 56,
+                "abridged": false
+            },
+            "blink_perf.dom": {
+                "abridged": false
+            },
             "blink_perf.events": {
-                "begin": 1,
                 "abridged": false
             },
             "blink_perf.image_decoder": {
                 "abridged": false
             },
             "blink_perf.layout": {
-                "end": 87,
+                "end": 46,
                 "abridged": false
             },
             "jetstream2": {
@@ -59,7 +67,7 @@
     "3": {
         "benchmarks": {
             "blink_perf.layout": {
-                "begin": 87,
+                "begin": 46,
                 "abridged": false
             },
             "blink_perf.owp_storage": {
@@ -69,10 +77,7 @@
                 "abridged": false
             },
             "blink_perf.parser": {
-                "abridged": false
-            },
-            "blink_perf.shadow_dom": {
-                "end": 12,
+                "end": 8,
                 "abridged": false
             },
             "jetstream2": {
@@ -85,17 +90,17 @@
     },
     "4": {
         "benchmarks": {
+            "blink_perf.parser": {
+                "begin": 8,
+                "abridged": false
+            },
             "blink_perf.shadow_dom": {
-                "begin": 12,
                 "abridged": false
             },
             "blink_perf.svg": {
                 "abridged": false
             },
             "blink_perf.webaudio": {
-                "abridged": false
-            },
-            "blink_perf.webcodecs": {
                 "end": 1,
                 "abridged": false
             },
@@ -109,10 +114,13 @@
     },
     "5": {
         "benchmarks": {
-            "blink_perf.webcodecs": {
+            "blink_perf.webaudio": {
                 "begin": 1,
                 "abridged": false
             },
+            "blink_perf.webcodecs": {
+                "abridged": false
+            },
             "blink_perf.webgl": {
                 "abridged": false
             },
@@ -126,7 +134,7 @@
                 "abridged": false
             },
             "desktop_ui": {
-                "end": 28,
+                "end": 7,
                 "abridged": false
             },
             "speedometer2": {
@@ -137,7 +145,7 @@
     "6": {
         "benchmarks": {
             "desktop_ui": {
-                "begin": 28,
+                "begin": 7,
                 "abridged": false
             },
             "dummy_benchmark.noisy_benchmark_1": {
@@ -156,7 +164,7 @@
                 "abridged": false
             },
             "loading.desktop": {
-                "end": 10,
+                "end": 4,
                 "abridged": false
             },
             "speedometer2": {
@@ -167,8 +175,8 @@
     "7": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 10,
-                "end": 28,
+                "begin": 4,
+                "end": 22,
                 "abridged": false
             },
             "speedometer2": {
@@ -179,8 +187,8 @@
     "8": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 28,
-                "end": 43,
+                "begin": 22,
+                "end": 37,
                 "abridged": false
             },
             "speedometer2": {
@@ -191,8 +199,8 @@
     "9": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 43,
-                "end": 57,
+                "begin": 37,
+                "end": 51,
                 "abridged": false
             },
             "speedometer2": {
@@ -203,8 +211,8 @@
     "10": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 57,
-                "end": 70,
+                "begin": 51,
+                "end": 64,
                 "abridged": false
             },
             "speedometer2": {
@@ -215,8 +223,8 @@
     "11": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 70,
-                "end": 82,
+                "begin": 64,
+                "end": 75,
                 "abridged": false
             },
             "speedometer2": {
@@ -227,8 +235,8 @@
     "12": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 82,
-                "end": 98,
+                "begin": 75,
+                "end": 88,
                 "abridged": false
             },
             "speedometer2": {
@@ -239,11 +247,8 @@
     "13": {
         "benchmarks": {
             "loading.desktop": {
-                "begin": 98,
-                "abridged": false
-            },
-            "media.desktop": {
-                "end": 19,
+                "begin": 88,
+                "end": 102,
                 "abridged": false
             },
             "speedometer2": {
@@ -253,17 +258,14 @@
     },
     "14": {
         "benchmarks": {
+            "loading.desktop": {
+                "begin": 102,
+                "abridged": false
+            },
             "media.desktop": {
-                "begin": 19,
                 "abridged": false
             },
             "memory.desktop": {
-                "abridged": false
-            },
-            "octane": {
-                "abridged": false
-            },
-            "power.desktop": {
                 "end": 2,
                 "abridged": false
             },
@@ -274,15 +276,15 @@
     },
     "15": {
         "benchmarks": {
-            "power.desktop": {
+            "memory.desktop": {
                 "begin": 2,
                 "abridged": false
             },
-            "rasterize_and_record_micro.top_25": {
+            "octane": {
                 "abridged": false
             },
-            "rendering.desktop": {
-                "end": 12,
+            "power.desktop": {
+                "end": 7,
                 "abridged": false
             },
             "speedometer2": {
@@ -292,9 +294,15 @@
     },
     "16": {
         "benchmarks": {
+            "power.desktop": {
+                "begin": 7,
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
             "rendering.desktop": {
-                "begin": 12,
-                "end": 53,
+                "end": 16,
                 "abridged": false
             },
             "speedometer2": {
@@ -305,8 +313,8 @@
     "17": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 53,
-                "end": 89,
+                "begin": 16,
+                "end": 55,
                 "abridged": false
             },
             "speedometer2": {
@@ -317,8 +325,8 @@
     "18": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 89,
-                "end": 113,
+                "begin": 55,
+                "end": 89,
                 "abridged": false
             },
             "speedometer2": {
@@ -329,8 +337,8 @@
     "19": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 113,
-                "end": 150,
+                "begin": 89,
+                "end": 112,
                 "abridged": false
             },
             "speedometer2": {
@@ -341,8 +349,8 @@
     "20": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 150,
-                "end": 174,
+                "begin": 112,
+                "end": 150,
                 "abridged": false
             }
         }
@@ -350,8 +358,8 @@
     "21": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 174,
-                "end": 212,
+                "begin": 150,
+                "end": 173,
                 "abridged": false
             }
         }
@@ -359,8 +367,8 @@
     "22": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 212,
-                "end": 250,
+                "begin": 173,
+                "end": 207,
                 "abridged": false
             }
         }
@@ -368,8 +376,8 @@
     "23": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 250,
-                "end": 285,
+                "begin": 207,
+                "end": 244,
                 "abridged": false
             }
         }
@@ -377,28 +385,28 @@
     "24": {
         "benchmarks": {
             "rendering.desktop": {
-                "begin": 285,
-                "abridged": false
-            },
-            "rendering.desktop.notracing": {
-                "end": 28,
+                "begin": 244,
+                "end": 277,
                 "abridged": false
             }
         }
     },
     "25": {
         "benchmarks": {
-            "rendering.desktop.notracing": {
-                "begin": 28,
-                "end": 193,
+            "rendering.desktop": {
+                "begin": 277,
+                "end": 320,
                 "abridged": false
             }
         }
     },
     "26": {
         "benchmarks": {
+            "rendering.desktop": {
+                "begin": 320,
+                "abridged": false
+            },
             "rendering.desktop.notracing": {
-                "begin": 193,
                 "abridged": false
             },
             "speedometer": {
@@ -412,16 +420,21 @@
             },
             "speedometer2-future": {
                 "abridged": false
+            },
+            "speedometer2-pcscan": {
+                "abridged": false
+            },
+            "system_health.common_desktop": {
+                "end": 20,
+                "abridged": false
             }
         }
     },
     "27": {
         "benchmarks": {
-            "speedometer2-pcscan": {
-                "abridged": false
-            },
             "system_health.common_desktop": {
-                "end": 43,
+                "begin": 20,
+                "end": 70,
                 "abridged": false
             }
         }
@@ -429,7 +442,11 @@
     "28": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 43,
+                "begin": 70,
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 9,
                 "abridged": false
             }
         }
@@ -437,7 +454,8 @@
     "29": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "end": 20,
+                "begin": 9,
+                "end": 27,
                 "abridged": false
             }
         }
@@ -445,8 +463,8 @@
     "30": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 20,
-                "end": 45,
+                "begin": 27,
+                "end": 53,
                 "abridged": false
             }
         }
@@ -454,8 +472,8 @@
     "31": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 45,
-                "end": 64,
+                "begin": 53,
+                "end": 65,
                 "abridged": false
             }
         }
@@ -463,8 +481,8 @@
     "32": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 64,
-                "end": 73,
+                "begin": 65,
+                "end": 76,
                 "abridged": false
             }
         }
@@ -472,7 +490,7 @@
     "33": {
         "benchmarks": {
             "system_health.memory_desktop": {
-                "begin": 73,
+                "begin": 76,
                 "abridged": false
             },
             "system_health.pcscan": {
@@ -485,7 +503,7 @@
                 "abridged": false
             },
             "v8.browsing_desktop": {
-                "end": 9,
+                "end": 13,
                 "abridged": false
             }
         }
@@ -493,8 +511,8 @@
     "34": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 9,
-                "end": 26,
+                "begin": 13,
+                "end": 28,
                 "abridged": false
             }
         }
@@ -502,11 +520,11 @@
     "35": {
         "benchmarks": {
             "v8.browsing_desktop": {
-                "begin": 26,
+                "begin": 28,
                 "abridged": false
             },
             "v8.browsing_desktop-future": {
-                "end": 19,
+                "end": 20,
                 "abridged": false
             }
         }
@@ -514,11 +532,11 @@
     "36": {
         "benchmarks": {
             "v8.browsing_desktop-future": {
-                "begin": 19,
+                "begin": 20,
                 "abridged": false
             },
             "v8.runtime_stats.top_25": {
-                "end": 11,
+                "end": 14,
                 "abridged": false
             }
         }
@@ -526,8 +544,8 @@
     "37": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 11,
-                "end": 50,
+                "begin": 14,
+                "end": 52,
                 "abridged": false
             }
         }
@@ -535,8 +553,8 @@
     "38": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 50,
-                "end": 89,
+                "begin": 52,
+                "end": 90,
                 "abridged": false
             }
         }
@@ -544,7 +562,7 @@
     "39": {
         "benchmarks": {
             "v8.runtime_stats.top_25": {
-                "begin": 89,
+                "begin": 90,
                 "abridged": false
             },
             "wasmpspdfkit": {
@@ -556,50 +574,50 @@
         }
     },
     "extra_infos": {
-        "num_stories": 1672,
-        "predicted_min_shard_time": 1548.0,
+        "num_stories": 1358,
+        "predicted_min_shard_time": 1377.0,
         "predicted_min_shard_index": 31,
-        "predicted_max_shard_time": 2121.0,
+        "predicted_max_shard_time": 2080.0,
         "predicted_max_shard_index": 39,
-        "shard #0": 1645.0,
-        "shard #1": 1645.0,
-        "shard #2": 1648.0,
-        "shard #3": 1705.0,
-        "shard #4": 1646.0,
-        "shard #5": 1657.0,
-        "shard #6": 1666.0,
-        "shard #7": 1700.0,
-        "shard #8": 1600.0,
-        "shard #9": 1638.0,
-        "shard #10": 1624.0,
-        "shard #11": 1664.0,
-        "shard #12": 1702.0,
-        "shard #13": 1636.0,
-        "shard #14": 1663.0,
-        "shard #15": 1631.0,
-        "shard #16": 1649.0,
-        "shard #17": 1622.0,
-        "shard #18": 1632.0,
-        "shard #19": 1627.0,
-        "shard #20": 1657.0,
-        "shard #21": 1649.0,
-        "shard #22": 1651.0,
-        "shard #23": 1684.0,
-        "shard #24": 1646.0,
-        "shard #25": 1650,
-        "shard #26": 1711.0,
-        "shard #27": 1631.0,
-        "shard #28": 1627.0,
-        "shard #29": 1686.0,
-        "shard #30": 1608.0,
-        "shard #31": 1548.0,
-        "shard #32": 1674.0,
-        "shard #33": 1665.0,
-        "shard #34": 1634.0,
-        "shard #35": 1600.0,
-        "shard #36": 1671.0,
-        "shard #37": 1661.0,
-        "shard #38": 1652.0,
-        "shard #39": 2121.0
+        "shard #0": 1569.0,
+        "shard #1": 1559.0,
+        "shard #2": 1558.0,
+        "shard #3": 1559.0,
+        "shard #4": 1537.0,
+        "shard #5": 1551.0,
+        "shard #6": 1573.0,
+        "shard #7": 1520.0,
+        "shard #8": 1598.0,
+        "shard #9": 1566.0,
+        "shard #10": 1566.0,
+        "shard #11": 1548.0,
+        "shard #12": 1608.0,
+        "shard #13": 1550.0,
+        "shard #14": 1530.0,
+        "shard #15": 1552.0,
+        "shard #16": 1564.0,
+        "shard #17": 1555.0,
+        "shard #18": 1552.0,
+        "shard #19": 1593.0,
+        "shard #20": 1524.0,
+        "shard #21": 1553.0,
+        "shard #22": 1557.0,
+        "shard #23": 1561.0,
+        "shard #24": 1556.0,
+        "shard #25": 1554.0,
+        "shard #26": 1585.0,
+        "shard #27": 1523.0,
+        "shard #28": 1602.0,
+        "shard #29": 1557.0,
+        "shard #30": 1557.0,
+        "shard #31": 1377.0,
+        "shard #32": 1575.0,
+        "shard #33": 1575.0,
+        "shard #34": 1558.0,
+        "shard #35": 1626.0,
+        "shard #36": 1592.0,
+        "shard #37": 1623.0,
+        "shard #38": 1605.0,
+        "shard #39": 2080.0
     }
 }
\ No newline at end of file
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 965e671e..d489a3d 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -140,7 +140,7 @@
     "java/res/color/default_text_color_secondary_list_baseline.xml",
     "java/res/color/filled_button_bg.xml",
     "java/res/color/filled_button_ripple_color.xml",
-    "java/res/color/text_button_ripple_color.xml",
+    "java/res/color/text_button_ripple_color_list_baseline.xml",
     "java/res/drawable-hdpi/btn_close.png",
     "java/res/drawable-hdpi/ic_expand_less_black_24dp.png",
     "java/res/drawable-hdpi/ic_expand_more_black_24dp.png",
diff --git a/ui/android/java/res/color/text_button_ripple_color.xml b/ui/android/java/res/color/text_button_ripple_color_list_baseline.xml
similarity index 100%
rename from ui/android/java/res/color/text_button_ripple_color.xml
rename to ui/android/java/res/color/text_button_ripple_color_list_baseline.xml
diff --git a/ui/android/java/res/values-v17/styles.xml b/ui/android/java/res/values-v17/styles.xml
index 018db0c..b4f57c3 100644
--- a/ui/android/java/res/values-v17/styles.xml
+++ b/ui/android/java/res/values-v17/styles.xml
@@ -52,7 +52,12 @@
         <item name="android:textAppearance">@style/TextAppearance.Button.Text.Blue</item>
         <item name="buttonTextColor">?attr/globalTextButtonTextColor</item>
         <item name="buttonColor">@android:color/transparent</item>
-        <item name="rippleColor">@color/text_button_ripple_color</item>
+        <!--
+          If ?attr/globalTextButtonRippleColor isn't defined in the theme, ButtonCompat will fall
+          back to a blue ripple color for buttons with transparent background and a white one for
+          the buttons with a solid background.
+        -->
+        <item name="rippleColor">?attr/globalTextButtonRippleColor</item>
         <item name="buttonRaised">false</item>
     </style>
     <style name="OutlinedButton" parent="TextButton" tools:ignore="UnusedResources">
diff --git a/ui/android/java/res/values/attrs.xml b/ui/android/java/res/values/attrs.xml
index a1f15e3..7f1f806 100644
--- a/ui/android/java/res/values/attrs.xml
+++ b/ui/android/java/res/values/attrs.xml
@@ -33,13 +33,14 @@
     </declare-styleable>
 
     <!-- The attributes prefixed with 'global' are used to control the button, link and URL colors
-         throughout the app. They are defined in ThemeOverlay.DynamicButtons and are applied
-         conditionally to the activity theme. This enables us to toggle dynamic colors for the
-         mentioned UI elements using a feature flag. These attributes may not be set in the themes,
-         so the code dealing with them should handle their absence. -->
+         throughout the app. They are defined in ThemeOverlay.DynamicButtons and are applied to the
+         activity theme. This enables us to apply dynamic colors to the mentioned UI elements. These
+         attributes may not be set in the themes, so the code dealing with them should handle their
+         absence. -->
     <attr name="globalFilledButtonBgColor" format="color"/>
     <attr name="globalFilledButtonTextColor" format="reference"/>
     <attr name="globalTextButtonTextColor" format="reference"/>
+    <attr name="globalTextButtonRippleColor" format="color"/>
     <attr name="globalOutlinedButtonBorderColor" format="color"/>
     <attr name="globalLinkTextColor" format="color"/>
     <attr name="globalClickableSpanColor" format="color"/>
diff --git a/ui/android/java/src/org/chromium/ui/widget/ButtonCompat.java b/ui/android/java/src/org/chromium/ui/widget/ButtonCompat.java
index ab74448..2584f58 100644
--- a/ui/android/java/src/org/chromium/ui/widget/ButtonCompat.java
+++ b/ui/android/java/src/org/chromium/ui/widget/ButtonCompat.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
 
@@ -67,8 +68,18 @@
                 attrs, R.styleable.ButtonCompat, android.R.attr.buttonStyle, 0);
         int buttonColorId = a.getResourceId(
                 R.styleable.ButtonCompat_buttonColor, R.color.blue_when_enabled_list);
-        int rippleColorId = a.getResourceId(
-                R.styleable.ButtonCompat_rippleColor, R.color.filled_button_ripple_color);
+
+        int rippleColorId = a.getResourceId(R.styleable.ButtonCompat_rippleColor, -1);
+        if (rippleColorId == -1) {
+            // If we can't resolve rippleColor, e.g. we're provided an attr that's not available in
+            // the theme, we'll use a fallback color based on the button color. A transparent color
+            // means a text button, which should have a blue ripple while a filled button should
+            // have a white ripple.
+            boolean isBgTransparent = getContext().getColor(buttonColorId) == Color.TRANSPARENT;
+            rippleColorId = isBgTransparent ? R.color.text_button_ripple_color_list_baseline
+                                            : R.color.filled_button_ripple_color;
+        }
+
         int borderColorId =
                 a.getResourceId(R.styleable.ButtonCompat_borderColor, android.R.color.transparent);
         int borderWidthId = a.getResourceId(R.styleable.ButtonCompat_borderWidth,
diff --git a/ui/android/resources/resource_manager.h b/ui/android/resources/resource_manager.h
index 9dc4fa86..bff2565 100644
--- a/ui/android/resources/resource_manager.h
+++ b/ui/android/resources/resource_manager.h
@@ -41,10 +41,16 @@
   virtual Resource* GetResource(AndroidResourceType res_type, int res_id) = 0;
 
   // Return a handle to a static resource specified by |res_id| that has a tint
-  // of |tint_color| applied to it.
+  // of |tint_color| applied to it. Does not retain the alpha of the tint color.
   virtual Resource* GetStaticResourceWithTint(int res_id,
                                               SkColor tint_color) = 0;
 
+  // Return a handle to a static resource specified by |res_id| that has a tint
+  // of |tint_color| applied to it.
+  virtual Resource* GetStaticResourceWithTint(int res_id,
+                                              SkColor tint_color,
+                                              bool preserve_color_alpha) = 0;
+
   // Trigger asynchronous loading of the resource specified by |res_type| and
   // |res_id|, if it has not yet been loaded.
   virtual void PreloadResource(AndroidResourceType res_type, int res_id) = 0;
diff --git a/ui/android/resources/resource_manager_impl.cc b/ui/android/resources/resource_manager_impl.cc
index bab86ad..ac65593 100644
--- a/ui/android/resources/resource_manager_impl.cc
+++ b/ui/android/resources/resource_manager_impl.cc
@@ -136,6 +136,13 @@
 
 Resource* ResourceManagerImpl::GetStaticResourceWithTint(int res_id,
                                                          SkColor tint_color) {
+  return GetStaticResourceWithTint(res_id, tint_color, false);
+}
+
+Resource* ResourceManagerImpl::GetStaticResourceWithTint(
+    int res_id,
+    SkColor tint_color,
+    bool preserve_color_alpha) {
   if (tinted_resources_.find(tint_color) == tinted_resources_.end()) {
     tinted_resources_[tint_color] = std::make_unique<ResourceMap>();
   }
@@ -162,13 +169,16 @@
   SkCanvas canvas(tinted_bitmap);
   canvas.clear(SK_ColorTRANSPARENT);
 
-  // Build a color filter to use on the base resource. This filter multiplies
-  // the RGB components by the components of the new color but retains the
-  // alpha of the original image.
+  // Build a color filter to use on the base resource. This filter ignores
+  // the original image's RGB components, instead using the components of the
+  // new color. The alpha of the original image will be conditionally preserved
+  // based on preserve_color_alpha.
+  float alpha_multiplier =
+      preserve_color_alpha ? SkColorGetA(tint_color) * (1.0f / 255) : 1;
   float color_matrix[20] = {0, 0, 0, 0, SkColorGetR(tint_color) * (1.0f / 255),
                             0, 0, 0, 0, SkColorGetG(tint_color) * (1.0f / 255),
                             0, 0, 0, 0, SkColorGetB(tint_color) * (1.0f / 255),
-                            0, 0, 0, 1, 0};
+                            0, 0, 0, alpha_multiplier, 0};
   SkPaint color_filter;
   color_filter.setColorFilter(SkColorFilters::Matrix(color_matrix));
 
diff --git a/ui/android/resources/resource_manager_impl.h b/ui/android/resources/resource_manager_impl.h
index e933a03..d5a1bb8 100644
--- a/ui/android/resources/resource_manager_impl.h
+++ b/ui/android/resources/resource_manager_impl.h
@@ -45,6 +45,9 @@
   Resource* GetResource(AndroidResourceType res_type, int res_id) override;
   Resource* GetStaticResourceWithTint(
       int res_id, SkColor tint_color) override;
+  Resource* GetStaticResourceWithTint(int res_id,
+                                      SkColor tint_color,
+                                      bool preserve_color_alpha) override;
   void PreloadResource(AndroidResourceType res_type, int res_id) override;
   void OnFrameUpdatesFinished() override;
 
diff --git a/ui/display/fake/fake_display_snapshot.cc b/ui/display/fake/fake_display_snapshot.cc
index 03d7b37..5b55a40 100644
--- a/ui/display/fake/fake_display_snapshot.cc
+++ b/ui/display/fake/fake_display_snapshot.cc
@@ -172,7 +172,8 @@
       has_color_correction_matrix_, color_correction_in_linear_space_, name_,
       std::move(modes_), current_mode_, native_mode_, product_code_,
       maximum_cursor_size_, color_space_, bits_per_channel_,
-      hdr_static_metadata_);
+      hdr_static_metadata_, variable_refresh_rate_state_,
+      vertical_display_range_limits_);
 }
 
 Builder& Builder::SetId(int64_t id) {
@@ -314,6 +315,18 @@
   return *this;
 }
 
+Builder& Builder::SetVariableRefreshRateState(
+    VariableRefreshRateState variable_refresh_rate_state) {
+  variable_refresh_rate_state_ = variable_refresh_rate_state;
+  return *this;
+}
+
+Builder& Builder::SetVerticalDisplayRangeLimits(
+    const absl::optional<gfx::Range>& vertical_display_range_limits) {
+  vertical_display_range_limits_ = vertical_display_range_limits;
+  return *this;
+}
+
 const DisplayMode* Builder::AddOrFindDisplayMode(const gfx::Size& size) {
   for (auto& mode : modes_) {
     if (mode->size() == size)
@@ -363,7 +376,9 @@
     const gfx::Size& maximum_cursor_size,
     const gfx::ColorSpace& color_space,
     uint32_t bits_per_channel,
-    const gfx::HDRStaticMetadata& hdr_static_metadata)
+    const gfx::HDRStaticMetadata& hdr_static_metadata,
+    VariableRefreshRateState variable_refresh_rate_state,
+    const absl::optional<gfx::Range>& vertical_display_range_limits)
     : DisplaySnapshot(display_id,
                       port_display_id,
                       edid_display_id,
@@ -390,7 +405,9 @@
                       native_mode,
                       product_code,
                       2018 /*year_of_manufacture */,
-                      maximum_cursor_size) {}
+                      maximum_cursor_size,
+                      variable_refresh_rate_state,
+                      vertical_display_range_limits) {}
 
 FakeDisplaySnapshot::~FakeDisplaySnapshot() {}
 
diff --git a/ui/display/fake/fake_display_snapshot.h b/ui/display/fake/fake_display_snapshot.h
index d51640d..c8a0b08 100644
--- a/ui/display/fake/fake_display_snapshot.h
+++ b/ui/display/fake/fake_display_snapshot.h
@@ -83,6 +83,10 @@
     Builder& SetBitsPerChannel(uint32_t bits_per_channel);
     Builder& SetHDRStaticMetadata(
         const gfx::HDRStaticMetadata& hdr_static_metadata);
+    Builder& SetVariableRefreshRateState(
+        VariableRefreshRateState variable_refresh_rate_state);
+    Builder& SetVerticalDisplayRangeLimits(
+        const absl::optional<gfx::Range>& vertical_display_range_limits);
 
    private:
     // Returns a display mode with |size|. If there is no existing mode, insert
@@ -115,31 +119,36 @@
     gfx::ColorSpace color_space_;
     uint32_t bits_per_channel_ = 8u;
     gfx::HDRStaticMetadata hdr_static_metadata_;
+    VariableRefreshRateState variable_refresh_rate_state_ = kVrrNotCapable;
+    absl::optional<gfx::Range> vertical_display_range_limits_ = absl::nullopt;
   };
 
-  FakeDisplaySnapshot(int64_t display_id,
-                      int64_t port_display_id,
-                      int64_t edid_display_id,
-                      uint16_t connector_index,
-                      const gfx::Point& origin,
-                      const gfx::Size& physical_size,
-                      DisplayConnectionType type,
-                      uint64_t base_connector_id,
-                      const std::vector<uint64_t>& path_topology,
-                      bool is_aspect_preserving_scaling,
-                      bool has_overscan,
-                      PrivacyScreenState privacy_screen_state,
-                      bool has_color_correction_matrix,
-                      bool color_correction_in_linear_space,
-                      std::string display_name,
-                      DisplayModeList modes,
-                      const DisplayMode* current_mode,
-                      const DisplayMode* native_mode,
-                      int64_t product_code,
-                      const gfx::Size& maximum_cursor_size,
-                      const gfx::ColorSpace& color_space,
-                      uint32_t bits_per_channel,
-                      const gfx::HDRStaticMetadata& hdr_static_metadata);
+  FakeDisplaySnapshot(
+      int64_t display_id,
+      int64_t port_display_id,
+      int64_t edid_display_id,
+      uint16_t connector_index,
+      const gfx::Point& origin,
+      const gfx::Size& physical_size,
+      DisplayConnectionType type,
+      uint64_t base_connector_id,
+      const std::vector<uint64_t>& path_topology,
+      bool is_aspect_preserving_scaling,
+      bool has_overscan,
+      PrivacyScreenState privacy_screen_state,
+      bool has_color_correction_matrix,
+      bool color_correction_in_linear_space,
+      std::string display_name,
+      DisplayModeList modes,
+      const DisplayMode* current_mode,
+      const DisplayMode* native_mode,
+      int64_t product_code,
+      const gfx::Size& maximum_cursor_size,
+      const gfx::ColorSpace& color_space,
+      uint32_t bits_per_channel,
+      const gfx::HDRStaticMetadata& hdr_static_metadata,
+      VariableRefreshRateState variable_refresh_rate_state,
+      const absl::optional<gfx::Range>& vertical_display_range_limits);
 
   FakeDisplaySnapshot(const FakeDisplaySnapshot&) = delete;
   FakeDisplaySnapshot& operator=(const FakeDisplaySnapshot&) = delete;
diff --git a/ui/display/manager/display_change_observer_unittest.cc b/ui/display/manager/display_change_observer_unittest.cc
index ab7c8ba..249f4486 100644
--- a/ui/display/manager/display_change_observer_unittest.cc
+++ b/ui/display/manager/display_change_observer_unittest.cc
@@ -202,7 +202,8 @@
       /*base_connector_id=*/1u, /*path_topology=*/{}, false, false,
       PrivacyScreenState::kNotSupported, false, false, std::string(), {},
       nullptr, nullptr, 0, gfx::Size(), gfx::ColorSpace(),
-      /*bits_per_channel=*/8u, /*hdr_static_metadata=*/{});
+      /*bits_per_channel=*/8u, /*hdr_static_metadata=*/{}, kVrrNotCapable,
+      absl::nullopt);
 
   ManagedDisplayInfo::ManagedDisplayModeList display_modes =
       DisplayChangeObserver::GetExternalManagedDisplayModeList(
diff --git a/ui/display/mojom/BUILD.gn b/ui/display/mojom/BUILD.gn
index 36e69eb..cc02460 100644
--- a/ui/display/mojom/BUILD.gn
+++ b/ui/display/mojom/BUILD.gn
@@ -24,6 +24,7 @@
     "//mojo/public/mojom/base",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
+    "//ui/gfx/range/mojom",
   ]
 
   shared_cpp_typemaps = [
@@ -96,6 +97,10 @@
           mojom = "display.mojom.PrivacyScreenState"
           cpp = "::display::PrivacyScreenState"
         },
+        {
+          mojom = "display.mojom.VariableRefreshRateState"
+          cpp = "::display::VariableRefreshRateState"
+        },
       ]
       traits_sources = [ "display_constants_mojom_traits.cc" ]
       traits_headers = [ "display_constants_mojom_traits.h" ]
diff --git a/ui/display/mojom/display_constants.mojom b/ui/display/mojom/display_constants.mojom
index 23713a6..42c3cfc 100644
--- a/ui/display/mojom/display_constants.mojom
+++ b/ui/display/mojom/display_constants.mojom
@@ -46,3 +46,10 @@
   ENABLED_LOCKED = 3,
   NOT_SUPPORTED = 4
 };
+
+// Corresponds to display::VariableRefreshRateState
+enum VariableRefreshRateState {
+  kVrrDisabled = 0,
+  kVrrEnabled = 1,
+  kVrrNotCapable = 2,
+};
diff --git a/ui/display/mojom/display_constants_mojom_traits.cc b/ui/display/mojom/display_constants_mojom_traits.cc
index 9c1f3d4c..4a62d91 100644
--- a/ui/display/mojom/display_constants_mojom_traits.cc
+++ b/ui/display/mojom/display_constants_mojom_traits.cc
@@ -239,4 +239,40 @@
   return false;
 }
 
+// static
+display::mojom::VariableRefreshRateState
+EnumTraits<display::mojom::VariableRefreshRateState,
+           display::VariableRefreshRateState>::
+    ToMojom(display::VariableRefreshRateState state) {
+  switch (state) {
+    case display::VariableRefreshRateState::kVrrDisabled:
+      return display::mojom::VariableRefreshRateState::kVrrDisabled;
+    case display::VariableRefreshRateState::kVrrEnabled:
+      return display::mojom::VariableRefreshRateState::kVrrEnabled;
+    case display::VariableRefreshRateState::kVrrNotCapable:
+      return display::mojom::VariableRefreshRateState::kVrrNotCapable;
+  }
+  NOTREACHED();
+  return display::mojom::VariableRefreshRateState::kVrrNotCapable;
+}
+
+// static
+bool EnumTraits<display::mojom::VariableRefreshRateState,
+                display::VariableRefreshRateState>::
+    FromMojom(display::mojom::VariableRefreshRateState state,
+              display::VariableRefreshRateState* out) {
+  switch (state) {
+    case display::mojom::VariableRefreshRateState::kVrrDisabled:
+      *out = display::VariableRefreshRateState::kVrrDisabled;
+      return true;
+    case display::mojom::VariableRefreshRateState::kVrrEnabled:
+      *out = display::VariableRefreshRateState::kVrrEnabled;
+      return true;
+    case display::mojom::VariableRefreshRateState::kVrrNotCapable:
+      *out = display::VariableRefreshRateState::kVrrNotCapable;
+      return true;
+  }
+  return false;
+}
+
 }  // namespace mojo
diff --git a/ui/display/mojom/display_constants_mojom_traits.h b/ui/display/mojom/display_constants_mojom_traits.h
index d77d804..6da91d21 100644
--- a/ui/display/mojom/display_constants_mojom_traits.h
+++ b/ui/display/mojom/display_constants_mojom_traits.h
@@ -52,6 +52,15 @@
                         display::PrivacyScreenState* out);
 };
 
+template <>
+struct EnumTraits<display::mojom::VariableRefreshRateState,
+                  display::VariableRefreshRateState> {
+  static display::mojom::VariableRefreshRateState ToMojom(
+      display::VariableRefreshRateState type);
+  static bool FromMojom(display::mojom::VariableRefreshRateState type,
+                        display::VariableRefreshRateState* out);
+};
+
 }  // namespace mojo
 
 #endif  // UI_DISPLAY_MOJOM_DISPLAY_CONSTANTS_MOJOM_TRAITS_H_
diff --git a/ui/display/mojom/display_mojom_traits_unittest.cc b/ui/display/mojom/display_mojom_traits_unittest.cc
index 6308d2a2..b6c4a024 100644
--- a/ui/display/mojom/display_mojom_traits_unittest.cc
+++ b/ui/display/mojom/display_mojom_traits_unittest.cc
@@ -279,6 +279,8 @@
   const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("a/cb");
   const int64_t product_code = 19;
   const int32_t year_of_manufacture = 1776;
+  const VariableRefreshRateState variable_refresh_rate_state = kVrrEnabled;
+  const gfx::Range vertical_display_range_limits({48, 120});
 
   const DisplayMode display_mode(gfx::Size(13, 11), true, 40.0f);
 
@@ -296,7 +298,8 @@
       has_color_correction_matrix, color_correction_in_linear_space,
       display_color_space, bits_per_channel, hdr_static_metadata, display_name,
       sys_path, std::move(modes), PanelOrientation::kNormal, edid, current_mode,
-      native_mode, product_code, year_of_manufacture, maximum_cursor_size);
+      native_mode, product_code, year_of_manufacture, maximum_cursor_size,
+      variable_refresh_rate_state, vertical_display_range_limits);
 
   std::unique_ptr<DisplaySnapshot> output;
   SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);
@@ -328,6 +331,8 @@
   const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("z/b");
   const int64_t product_code = 9;
   const int32_t year_of_manufacture = 1776;
+  const VariableRefreshRateState variable_refresh_rate_state = kVrrEnabled;
+  const gfx::Range vertical_display_range_limits({48, 120});
 
   const DisplayMode display_mode(gfx::Size(13, 11), true, 50.0f);
 
@@ -345,7 +350,8 @@
       has_color_correction_matrix, color_correction_in_linear_space,
       display_color_space, bits_per_channel, hdr_static_metadata, display_name,
       sys_path, std::move(modes), PanelOrientation::kNormal, edid, current_mode,
-      native_mode, product_code, year_of_manufacture, maximum_cursor_size);
+      native_mode, product_code, year_of_manufacture, maximum_cursor_size,
+      variable_refresh_rate_state, vertical_display_range_limits);
 
   std::unique_ptr<DisplaySnapshot> output;
   SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);
@@ -377,6 +383,8 @@
   const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("a/cb");
   const int64_t product_code = 139;
   const int32_t year_of_manufacture = 2018;
+  const VariableRefreshRateState variable_refresh_rate_state = kVrrDisabled;
+  const gfx::Range vertical_display_range_limits({40, 144});
 
   const DisplayMode display_mode(gfx::Size(1024, 768), false, 60.0f);
   const DisplayMode display_current_mode(gfx::Size(1440, 900), false, 59.89f);
@@ -398,7 +406,8 @@
       has_color_correction_matrix, color_correction_in_linear_space,
       display_color_space, bits_per_channel, hdr_static_metadata, display_name,
       sys_path, std::move(modes), PanelOrientation::kLeftUp, edid, current_mode,
-      native_mode, product_code, year_of_manufacture, maximum_cursor_size);
+      native_mode, product_code, year_of_manufacture, maximum_cursor_size,
+      variable_refresh_rate_state, vertical_display_range_limits);
 
   std::unique_ptr<DisplaySnapshot> output;
   SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);
@@ -430,6 +439,7 @@
   const base::FilePath sys_path;
   const int64_t product_code = 139;
   const int32_t year_of_manufacture = 2018;
+  const VariableRefreshRateState variable_refresh_rate_state = kVrrNotCapable;
 
   const DisplayMode display_mode(gfx::Size(2560, 1700), false, 95.96f);
 
@@ -448,7 +458,7 @@
       display_color_space, bits_per_channel, hdr_static_metadata, display_name,
       sys_path, std::move(modes), PanelOrientation::kRightUp, edid,
       current_mode, native_mode, product_code, year_of_manufacture,
-      maximum_cursor_size);
+      maximum_cursor_size, variable_refresh_rate_state, absl::nullopt);
 
   std::unique_ptr<DisplaySnapshot> output;
   SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);
diff --git a/ui/display/mojom/display_snapshot.mojom b/ui/display/mojom/display_snapshot.mojom
index 37aac64..76eec0a 100644
--- a/ui/display/mojom/display_snapshot.mojom
+++ b/ui/display/mojom/display_snapshot.mojom
@@ -10,6 +10,7 @@
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/mojom/color_space.mojom";
 import "ui/gfx/mojom/hdr_static_metadata.mojom";
+import "ui/gfx/range/mojom/range.mojom";
 
 // Corresponds to display::DisplaySnapshot.
 struct DisplaySnapshot {
@@ -43,4 +44,6 @@
   int64 product_code;
   int32 year_of_manufacture;
   gfx.mojom.Size maximum_cursor_size;
+  VariableRefreshRateState variable_refresh_rate_state;
+  gfx.mojom.Range? vertical_display_range_limits;
 };
diff --git a/ui/display/mojom/display_snapshot_mojom_traits.cc b/ui/display/mojom/display_snapshot_mojom_traits.cc
index fcb8e23..72cf0759 100644
--- a/ui/display/mojom/display_snapshot_mojom_traits.cc
+++ b/ui/display/mojom/display_snapshot_mojom_traits.cc
@@ -147,6 +147,14 @@
   if (!data.ReadMaximumCursorSize(&maximum_cursor_size))
     return false;
 
+  display::VariableRefreshRateState variable_refresh_rate_state;
+  if (!data.ReadVariableRefreshRateState(&variable_refresh_rate_state))
+    return false;
+
+  absl::optional<gfx::Range> vertical_display_range_limits;
+  if (!data.ReadVerticalDisplayRangeLimits(&vertical_display_range_limits))
+    return false;
+
   *out = std::make_unique<display::DisplaySnapshot>(
       data.display_id(), data.port_display_id(), data.edid_display_id(),
       data.connector_index(), origin, physical_size, type,
@@ -157,7 +165,8 @@
       data.bits_per_channel(), hdr_static_metadata, display_name, file_path,
       std::move(modes), panel_orientation, std::move(edid), current_mode,
       native_mode, data.product_code(), data.year_of_manufacture(),
-      maximum_cursor_size);
+      maximum_cursor_size, variable_refresh_rate_state,
+      vertical_display_range_limits);
   return true;
 }
 
diff --git a/ui/display/mojom/display_snapshot_mojom_traits.h b/ui/display/mojom/display_snapshot_mojom_traits.h
index 4bff463..2771de5 100644
--- a/ui/display/mojom/display_snapshot_mojom_traits.h
+++ b/ui/display/mojom/display_snapshot_mojom_traits.h
@@ -157,6 +157,16 @@
     return snapshot->maximum_cursor_size();
   }
 
+  static display::VariableRefreshRateState variable_refresh_rate_state(
+      const std::unique_ptr<display::DisplaySnapshot>& snapshot) {
+    return snapshot->variable_refresh_rate_state();
+  }
+
+  static const absl::optional<gfx::Range>& vertical_display_range_limits(
+      const std::unique_ptr<display::DisplaySnapshot>& snapshot) {
+    return snapshot->vertical_display_range_limits();
+  }
+
   static bool Read(display::mojom::DisplaySnapshotDataView data,
                    std::unique_ptr<display::DisplaySnapshot>* out);
 };
diff --git a/ui/display/types/BUILD.gn b/ui/display/types/BUILD.gn
index 257fb985..d148b094 100644
--- a/ui/display/types/BUILD.gn
+++ b/ui/display/types/BUILD.gn
@@ -28,5 +28,6 @@
     "//ui/gfx:color_space",
     "//ui/gfx:memory_buffer",
     "//ui/gfx/geometry",
+    "//ui/gfx/range",
   ]
 }
diff --git a/ui/display/types/DEPS b/ui/display/types/DEPS
index d58763e..e1edd22 100644
--- a/ui/display/types/DEPS
+++ b/ui/display/types/DEPS
@@ -5,4 +5,5 @@
   "+ui/gfx/color_space.h",
   "+ui/gfx/hdr_static_metadata.h",
   "+ui/gfx/geometry",
+  "+ui/gfx/range/range.h",
 ]
diff --git a/ui/display/types/display_constants.h b/ui/display/types/display_constants.h
index 8a71d8e..0be24d69 100644
--- a/ui/display/types/display_constants.h
+++ b/ui/display/types/display_constants.h
@@ -144,6 +144,13 @@
   kSeamlessModeset = 1 << 2,
 };
 
+enum VariableRefreshRateState {
+  kVrrDisabled = 0,
+  kVrrEnabled = 1,
+  kVrrNotCapable = 2,
+  kVrrLast = kVrrNotCapable,
+};
+
 // Defines the float values closest to repeating decimal scale factors.
 constexpr float kDsf_1_777 = 1.77777779102325439453125f;
 constexpr float kDsf_2_252 = 2.2522523403167724609375f;
diff --git a/ui/display/types/display_snapshot.cc b/ui/display/types/display_snapshot.cc
index c906cb0..97bb96f6 100644
--- a/ui/display/types/display_snapshot.cc
+++ b/ui/display/types/display_snapshot.cc
@@ -88,7 +88,9 @@
     const DisplayMode* native_mode,
     int64_t product_code,
     int32_t year_of_manufacture,
-    const gfx::Size& maximum_cursor_size)
+    const gfx::Size& maximum_cursor_size,
+    VariableRefreshRateState variable_refresh_rate_state,
+    const absl::optional<gfx::Range>& vertical_display_range_limits)
     : display_id_(display_id),
       port_display_id_(port_display_id),
       edid_display_id_(edid_display_id),
@@ -115,11 +117,12 @@
       native_mode_(native_mode),
       product_code_(product_code),
       year_of_manufacture_(year_of_manufacture),
-      maximum_cursor_size_(maximum_cursor_size) {
+      maximum_cursor_size_(maximum_cursor_size),
+      variable_refresh_rate_state_(variable_refresh_rate_state),
+      vertical_display_range_limits_(vertical_display_range_limits) {
   // We must explicitly clear out the bytes that represent the serial number.
-  const size_t end =
-      std::min(kSerialNumberBeginingByte + kSerialNumberLengthInBytes,
-               edid_.size());
+  const size_t end = std::min(
+      kSerialNumberBeginingByte + kSerialNumberLengthInBytes, edid_.size());
   for (size_t i = kSerialNumberBeginingByte; i < end; ++i)
     edid_[i] = 0;
 }
@@ -149,7 +152,8 @@
       color_space_, bits_per_channel_, hdr_static_metadata_, display_name_,
       sys_path_, std::move(clone_modes), panel_orientation_, edid_,
       cloned_current_mode, cloned_native_mode, product_code_,
-      year_of_manufacture_, maximum_cursor_size_);
+      year_of_manufacture_, maximum_cursor_size_, variable_refresh_rate_state_,
+      vertical_display_range_limits_);
 }
 
 std::string DisplaySnapshot::ToString() const {
diff --git a/ui/display/types/display_snapshot.h b/ui/display/types/display_snapshot.h
index fe342d6..4866edb3 100644
--- a/ui/display/types/display_snapshot.h
+++ b/ui/display/types/display_snapshot.h
@@ -21,6 +21,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/hdr_static_metadata.h"
+#include "ui/gfx/range/range.h"
 
 namespace display {
 
@@ -58,7 +59,9 @@
       const DisplayMode* native_mode,
       int64_t product_code,
       int32_t year_of_manufacture,
-      const gfx::Size& maximum_cursor_size);
+      const gfx::Size& maximum_cursor_size,
+      VariableRefreshRateState variable_refresh_rate_state,
+      const absl::optional<gfx::Range>& vertical_display_range_limits);
 
   DisplaySnapshot(const DisplaySnapshot&) = delete;
   DisplaySnapshot& operator=(const DisplaySnapshot&) = delete;
@@ -109,6 +112,12 @@
   int64_t product_code() const { return product_code_; }
   int32_t year_of_manufacture() const { return year_of_manufacture_; }
   const gfx::Size& maximum_cursor_size() const { return maximum_cursor_size_; }
+  VariableRefreshRateState variable_refresh_rate_state() const {
+    return variable_refresh_rate_state_;
+  }
+  const absl::optional<gfx::Range>& vertical_display_range_limits() const {
+    return vertical_display_range_limits_;
+  }
 
   void add_mode(const DisplayMode* mode) { modes_.push_back(mode->Clone()); }
 
@@ -236,6 +245,12 @@
 
   // Maximum supported cursor size on this display.
   const gfx::Size maximum_cursor_size_;
+
+  // Whether VRR is enabled, disabled, or not capable on this display.
+  const VariableRefreshRateState variable_refresh_rate_state_;
+  // The supported vrefresh frequency range for this display. Omitted if this
+  // display is not VRR capable.
+  const absl::optional<gfx::Range> vertical_display_range_limits_;
 };
 
 }  // namespace display
diff --git a/ui/display/util/BUILD.gn b/ui/display/util/BUILD.gn
index cf91a3c..33731b1 100644
--- a/ui/display/util/BUILD.gn
+++ b/ui/display/util/BUILD.gn
@@ -26,6 +26,7 @@
     "//ui/display/types",
     "//ui/gfx:color_space",
     "//ui/gfx/geometry",
+    "//ui/gfx/range",
   ]
 
   if (is_chromeos_ash) {
diff --git a/ui/display/util/edid_parser.cc b/ui/display/util/edid_parser.cc
index 0433a9c..0f78c004 100644
--- a/ui/display/util/edid_parser.cc
+++ b/ui/display/util/edid_parser.cc
@@ -468,26 +468,29 @@
     if (edid[offset] == 0 && edid[offset + 1] == 0 && edid[offset + 2] == 0 &&
         edid[offset + 3] == kDisplayRangeLimitsDescriptor) {
       // byte 4: Offsets for display range limits
-      const uint8_t kRateOffset = edid[offset + 4];
+      const uint8_t rateOffset = edid[offset + 4];
       // bits 7-4: Reserved \0
-      if (kRateOffset & 0x10)
+      if (rateOffset & 0xf0)
         continue;
       // bit 3: Horizontal max rate offset (not used)
       // bit 2: Horizontal min rate offset (not used)
       // bit 1: Vertical max rate offset
-      const uint8_t verticalMaxRateOffset = kRateOffset & (1 << 1) ? 255 : 0;
+      const uint8_t verticalMaxRateOffset = rateOffset & (1 << 1) ? 255 : 0;
       // bit 0: Vertical min rate offset
-      const uint8_t verticalMinRateOffset = kRateOffset & (1 << 0) ? 255 : 0;
+      const uint8_t verticalMinRateOffset = rateOffset & (1 << 0) ? 255 : 0;
 
       // bytes 5-8: Rate limits
       // Each byte must be within [1, 255].
       if (edid[offset + 5] == 0 || edid[offset + 6] == 0 ||
           edid[offset + 7] == 0 || edid[offset + 8] == 0)
         continue;
+      vertical_display_range_limits_ = absl::make_optional<gfx::Range>();
       // byte 5: Min vertical rate in Hz
-      min_vfreq_ = edid[offset + 5] + verticalMinRateOffset;
+      vertical_display_range_limits_->set_start(edid[offset + 5] +
+                                                verticalMinRateOffset);
       // byte 6: Max vertical rate in Hz
-      max_vfreq_ = edid[offset + 6] + verticalMaxRateOffset;
+      vertical_display_range_limits_->set_end(edid[offset + 6] +
+                                              verticalMaxRateOffset);
       // byte 7: Min horizontal rate in kHz (not used)
       // byte 8: Max horizontal rate in kHz (not used)
 
diff --git a/ui/display/util/edid_parser.h b/ui/display/util/edid_parser.h
index 1b82695c..b4baa0a1 100644
--- a/ui/display/util/edid_parser.h
+++ b/ui/display/util/edid_parser.h
@@ -18,6 +18,7 @@
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/hdr_static_metadata.h"
+#include "ui/gfx/range/range.h"
 
 namespace display {
 
@@ -67,8 +68,9 @@
   const absl::optional<gfx::HDRStaticMetadata>& hdr_static_metadata() const {
     return hdr_static_metadata_;
   }
-  const absl::optional<uint16_t>& min_vfreq() const { return min_vfreq_; }
-  const absl::optional<uint16_t>& max_vfreq() const { return max_vfreq_; }
+  const absl::optional<gfx::Range>& vertical_display_range_limits() const {
+    return vertical_display_range_limits_;
+  }
   // Returns a 32-bit identifier for this display |manufacturer_id_| and
   // |product_id_|.
   uint32_t GetProductCode() const;
@@ -138,8 +140,7 @@
   base::flat_set<gfx::ColorSpace::PrimaryID> supported_color_primary_ids_;
   base::flat_set<gfx::ColorSpace::TransferID> supported_color_transfer_ids_;
   absl::optional<gfx::HDRStaticMetadata> hdr_static_metadata_;
-  absl::optional<uint16_t> min_vfreq_;
-  absl::optional<uint16_t> max_vfreq_;
+  absl::optional<gfx::Range> vertical_display_range_limits_;
 
   uint32_t audio_formats_;
 };
diff --git a/ui/display/util/edid_parser_unittest.cc b/ui/display/util/edid_parser_unittest.cc
index e6d6305..720a842 100644
--- a/ui/display/util/edid_parser_unittest.cc
+++ b/ui/display/util/edid_parser_unittest.cc
@@ -341,8 +341,7 @@
   base::flat_set<gfx::ColorSpace::PrimaryID> supported_color_primary_ids_;
   base::flat_set<gfx::ColorSpace::TransferID> supported_color_transfer_ids_;
   absl::optional<gfx::HDRStaticMetadata> hdr_static_metadata_;
-  absl::optional<uint16_t> min_vfreq;
-  absl::optional<uint16_t> max_vfreq;
+  absl::optional<gfx::Range> vertical_display_range_limits_;
 
   const unsigned char* edid_blob;
   size_t edid_blob_length;
@@ -369,7 +368,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kBadDisplayName,
      kBadDisplayNameLength},
     {0x22f0u,
@@ -394,7 +392,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kNormalDisplay,
      kNormalDisplayLength},
     {0x22f0u,
@@ -419,7 +416,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kNoMaxImageSizeDisplay,
      kNoMaxImageSizeDisplayLength},
     {0x22f0u,
@@ -444,7 +440,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kBlockZeroSerialNumberOnlyDisplay,
      kBlockZeroSerialNumberOnlyDisplayLength},
     {0x22f0u,
@@ -469,7 +464,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kNoSerialNumberDisplay,
      kNoSerialNumberDisplayLength},
     {0x22f0u,
@@ -494,7 +488,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kNoWeekOfManufactureDisplay,
      kNoWeekOfManufactureDisplayLength},
     {0x22f0u,
@@ -519,7 +512,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kModelYearDisplay,
      kModelYearDisplayLength},
     {0x4ca3u,
@@ -544,7 +536,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kInternalDisplay,
      kInternalDisplayLength},
     {0x4c2du,
@@ -568,8 +559,7 @@
      {},
      {},
      absl::nullopt,
-     24,
-     75,
+     gfx::Range(24, 75),
      kOverscanDisplay,
      kOverscanDisplayLength},
     {0x10ACu,
@@ -593,8 +583,7 @@
      {gfx::ColorSpace::PrimaryID::BT709, gfx::ColorSpace::PrimaryID::SMPTE170M},
      {},
      absl::nullopt,
-     49,
-     86,
+     gfx::Range(49, 86),
      kMisdetectedDisplay,
      kMisdetectedDisplayLength},
     {0x22f0u,
@@ -618,8 +607,7 @@
      {},
      {},
      absl::nullopt,
-     48,
-     85,
+     gfx::Range(48, 85),
      kLP2565A,
      kLP2565ALength},
     {0x22f0u,
@@ -643,8 +631,7 @@
      {},
      {},
      absl::nullopt,
-     48,
-     85,
+     gfx::Range(48, 85),
      kLP2565B,
      kLP2565BLength},
     {0x22f0u,
@@ -668,8 +655,7 @@
      {},
      {},
      absl::nullopt,
-     24,
-     60,
+     gfx::Range(24, 60),
      kHPz32x,
      kHPz32xLength},
     {0x30E4u,
@@ -694,7 +680,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kSamus,
      kSamusLength},
     {0x4D10u,
@@ -719,7 +704,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      kEve,
      kEveLength},
     {19501u,
@@ -745,8 +729,7 @@
      {gfx::ColorSpace::TransferID::BT709, gfx::ColorSpace::TransferID::PQ,
       gfx::ColorSpace::TransferID::HLG},
      absl::make_optional<gfx::HDRStaticMetadata>(603.666, 530.095, 0.00454),
-     24,
-     75,
+     gfx::Range(24, 75),
      kHDRMetadata,
      kHDRMetadataLength},
 
@@ -774,7 +757,6 @@
      {},
      absl::nullopt,
      absl::nullopt,
-     absl::nullopt,
      nullptr,
      0u},
 };
@@ -842,8 +824,17 @@
                 epsilon);
   }
 
-  EXPECT_EQ(parser_.min_vfreq(), GetParam().min_vfreq);
-  EXPECT_EQ(parser_.max_vfreq(), GetParam().max_vfreq);
+  const absl::optional<gfx::Range> vertical_display_range_limits =
+      parser_.vertical_display_range_limits();
+  EXPECT_EQ(GetParam().vertical_display_range_limits_.has_value(),
+            vertical_display_range_limits.has_value());
+  if (GetParam().vertical_display_range_limits_.has_value() &&
+      vertical_display_range_limits.has_value()) {
+    EXPECT_EQ(vertical_display_range_limits->start(),
+              GetParam().vertical_display_range_limits_->start());
+    EXPECT_EQ(vertical_display_range_limits->end(),
+              GetParam().vertical_display_range_limits_->end());
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(All, EDIDParserTest, ValuesIn(kTestCases));
diff --git a/ui/events/x/x11_event_translation.cc b/ui/events/x/x11_event_translation.cc
index 0f0ce74..dd8c160 100644
--- a/ui/events/x/x11_event_translation.cc
+++ b/ui/events/x/x11_event_translation.cc
@@ -194,6 +194,15 @@
     GetFlingDataFromXEvent(xev, &x_offset, &y_offset, &x_offset_ordinal,
                            &y_offset_ordinal, nullptr);
   }
+  // When lifting up fingers x_offset and y_offset both have the value 0
+  // If this is the case ET_SCROLL_FLING_START needs to be emitted, in order to
+  // trigger touchpad overscroll navigation gesture.
+  // x_offset and y_offset should not be manipulated, however, since some X11
+  // drivers such as synaptics simulate the fling themselves
+  if (!x_offset && !y_offset) {
+    type = ET_SCROLL_FLING_START;
+  }
+
   auto event = std::make_unique<ScrollEvent>(
       type, EventLocationFromXEvent(xev), EventTimeFromXEvent(xev),
       EventFlagsFromXEvent(xev), x_offset, y_offset, x_offset_ordinal,
@@ -203,7 +212,8 @@
   // We need to filter zero scroll offset here. Because MouseWheelEventQueue
   // assumes we'll never get a zero scroll offset event and we need delta to
   // determine which element to scroll on phaseBegan.
-  return (event->x_offset() != 0.0 || event->y_offset() != 0.0)
+  return (event->x_offset() != 0.0 || event->y_offset() != 0.0 ||
+          event->type() == ET_SCROLL_FLING_START)
              ? std::move(event)
              : nullptr;
 }
diff --git a/ui/ozone/platform/drm/common/drm_util.cc b/ui/ozone/platform/drm/common/drm_util.cc
index 127e5538..2dacd791 100644
--- a/ui/ozone/platform/drm/common/drm_util.cc
+++ b/ui/ozone/platform/drm/common/drm_util.cc
@@ -344,10 +344,6 @@
 }
 
 bool IsVrrCapable(int fd, drmModeConnector* connector) {
-  if (!features::IsVariableRefreshRateEnabled()) {
-    return false;
-  }
-
   ScopedDrmPropertyPtr vrr_capable_property;
   const int vrr_capable_index = GetDrmProperty(
       fd, connector, kVrrCapablePropertyName, &vrr_capable_property);
@@ -355,10 +351,6 @@
 }
 
 bool IsVrrEnabled(int fd, drmModeCrtc* crtc) {
-  if (!features::IsVariableRefreshRateEnabled()) {
-    return false;
-  }
-
   ScopedDrmObjectPropertyPtr crtc_props(
       drmModeObjectGetProperties(fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC));
   ScopedDrmPropertyPtr vrr_enabled_property;
@@ -367,6 +359,18 @@
   return vrr_enabled_index >= 0 && crtc_props->prop_values[vrr_enabled_index];
 }
 
+display::VariableRefreshRateState GetVariableRefreshRateState(
+    int fd,
+    HardwareDisplayControllerInfo* info) {
+  if (!IsVrrCapable(fd, info->connector()))
+    return display::kVrrNotCapable;
+
+  if (IsVrrEnabled(fd, info->crtc()))
+    return display::kVrrEnabled;
+
+  return display::kVrrDisabled;
+}
+
 HardwareDisplayControllerInfo::HardwareDisplayControllerInfo(
     ScopedDrmConnectorPtr connector,
     ScopedDrmCrtcPtr crtc,
@@ -562,6 +566,8 @@
   const bool color_correction_in_linear_space =
       has_color_correction_matrix && GetDrmDriverNameFromFd(fd) == "rockchip";
   const gfx::Size maximum_cursor_size = GetMaximumCursorSize(fd);
+  const display::VariableRefreshRateState variable_refresh_rate_state =
+      GetVariableRefreshRateState(fd, info);
 
   std::string display_name;
   // Make sure the ID contains non index part.
@@ -575,6 +581,7 @@
   absl::optional<gfx::HDRStaticMetadata> hdr_static_metadata{};
   // Active pixels size from the first detailed timing descriptor in the EDID.
   gfx::Size active_pixel_size;
+  absl::optional<gfx::Range> vertical_display_range_limits;
 
   ScopedDrmPropertyBlobPtr edid_blob(
       GetDrmPropertyBlob(fd, info->connector(), "EDID"));
@@ -602,6 +609,10 @@
     base::UmaHistogramCounts100("DrmUtil.CreateDisplaySnapshot.BitsPerChannel",
                                 bits_per_channel);
     hdr_static_metadata = edid_parser.hdr_static_metadata();
+    vertical_display_range_limits =
+        variable_refresh_rate_state == display::kVrrNotCapable
+            ? absl::nullopt
+            : edid_parser.vertical_display_range_limits();
   } else {
     VLOG(1) << "Failed to get EDID blob for connector "
             << info->connector()->connector_id;
@@ -619,7 +630,8 @@
       has_color_correction_matrix, color_correction_in_linear_space,
       display_color_space, bits_per_channel, hdr_static_metadata, display_name,
       sys_path, std::move(modes), panel_orientation, edid, current_mode,
-      native_mode, product_code, year_of_manufacture, maximum_cursor_size);
+      native_mode, product_code, year_of_manufacture, maximum_cursor_size,
+      variable_refresh_rate_state, vertical_display_range_limits);
 }
 
 int GetFourCCFormatForOpaqueFramebuffer(gfx::BufferFormat format) {
diff --git a/ui/ozone/platform/drm/common/drm_util.h b/ui/ozone/platform/drm/common/drm_util.h
index 10f7713..05ff0c4 100644
--- a/ui/ozone/platform/drm/common/drm_util.h
+++ b/ui/ozone/platform/drm/common/drm_util.h
@@ -163,6 +163,10 @@
 
 bool IsVrrEnabled(int fd, drmModeCrtc* crtc);
 
+display::VariableRefreshRateState GetVariableRefreshRateState(
+    int fd,
+    HardwareDisplayControllerInfo* info);
+
 uint64_t GetEnumValueForName(int fd, int property_id, const char* str);
 
 std::vector<uint64_t> ParsePathBlob(const drmModePropertyBlobRes& path_blob);
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index bf431e7..219c81da 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -521,6 +521,7 @@
   deps = [
     ":wayland",
     "//base:base",
+    "//ui/base/wayland:wayland_display_util",
     "//ui/ozone:platform",
     "//ui/ozone:test_support",
   ]
@@ -603,6 +604,7 @@
     "//ui/base/dragdrop:types",
     "//ui/base/dragdrop/mojom",
     "//ui/base/ime/linux",
+    "//ui/base/wayland:wayland_display_util",
     "//ui/events/ozone/layout",
     "//ui/gfx/linux:test_support",
     "//ui/ozone:platform",
diff --git a/ui/ozone/platform/wayland/common/wayland_object.cc b/ui/ozone/platform/wayland/common/wayland_object.cc
index 1c621e2..54edc6a 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.cc
+++ b/ui/ozone/platform/wayland/common/wayland_object.cc
@@ -156,14 +156,6 @@
   return true;
 }
 
-uint32_t CalculateBindVersion(uint32_t impl_version,
-                              uint32_t compositor_version,
-                              int libwayland_version) {
-  return std::min(
-      impl_version,
-      std::min(compositor_version, static_cast<uint32_t>(libwayland_version)));
-}
-
 void (*ObjectTraits<wl_cursor_theme>::deleter)(wl_cursor_theme*) =
     &wl_cursor_theme_destroy;
 
diff --git a/ui/ozone/platform/wayland/common/wayland_object.h b/ui/ozone/platform/wayland/common/wayland_object.h
index 463320e..5f486509 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.h
+++ b/ui/ozone/platform/wayland/common/wayland_object.h
@@ -88,19 +88,6 @@
              uint32_t min_version,
              uint32_t max_version);
 
-// Calculates the version of an interface that we want to bind to, given:
-// - |impl_version|, the maximum version that we have implemented support for;
-// - |compositor_version|, the maximum version advertised by the compositor; and
-// - |libwayland_version|, the maximum version that libwayland-client can handle
-//   (can be accessed using INTERFACE_NAME_interface.version). Binding to a
-//   version higher than this would lead to an "interface X has no event Y"
-//   runtime error. This is particularly relevant when building with
-//   use_system_libwayland = true.
-// The result is the minimum of the three versions.
-uint32_t CalculateBindVersion(uint32_t impl_version,
-                              uint32_t compositor_version,
-                              int libwayland_version);
-
 }  // namespace wl
 
 // Puts the forward declaration for struct TYPE and declares the template
diff --git a/ui/ozone/platform/wayland/host/gtk_shell1.cc b/ui/ozone/platform/wayland/host/gtk_shell1.cc
index da909fe..041c71b 100644
--- a/ui/ozone/platform/wayland/host/gtk_shell1.cc
+++ b/ui/ozone/platform/wayland/host/gtk_shell1.cc
@@ -38,10 +38,8 @@
     return;
   }
 
-  auto gtk_shell1 = wl::Bind<::gtk_shell1>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               gtk_shell1_interface.version));
+  auto gtk_shell1 =
+      wl::Bind<::gtk_shell1>(registry, name, std::min(version, kMaxVersion));
   if (!gtk_shell1) {
     LOG(ERROR) << "Failed to bind gtk_shell1";
     return;
diff --git a/ui/ozone/platform/wayland/host/surface_augmenter.cc b/ui/ozone/platform/wayland/host/surface_augmenter.cc
index c573309..a7769414 100644
--- a/ui/ozone/platform/wayland/host/surface_augmenter.cc
+++ b/ui/ozone/platform/wayland/host/surface_augmenter.cc
@@ -35,10 +35,8 @@
     return;
   }
 
-  auto augmenter = wl::Bind<surface_augmenter>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               surface_augmenter_interface.version));
+  auto augmenter = wl::Bind<surface_augmenter>(registry, name,
+                                               std::min(version, kMaxVersion));
   if (!augmenter) {
     LOG(ERROR) << "Failed to bind surface_augmenter";
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index a1e5850..241d80a 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -4,19 +4,9 @@
 
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 
-#include <alpha-compositing-unstable-v1-client-protocol.h>
 #include <content-type-v1-client-protocol.h>
 #include <extended-drag-unstable-v1-client-protocol.h>
-#include <keyboard-extension-unstable-v1-client-protocol.h>
-#include <keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h>
-#include <linux-explicit-synchronization-unstable-v1-client-protocol.h>
 #include <presentation-time-client-protocol.h>
-#include <stylus-unstable-v2-client-protocol.h>
-#include <text-input-extension-unstable-v1-client-protocol.h>
-#include <text-input-unstable-v1-client-protocol.h>
-#include <viewporter-client-protocol.h>
-#include <xdg-decoration-unstable-v1-client-protocol.h>
-#include <xdg-output-unstable-v1-client-protocol.h>
 #include <xdg-shell-client-protocol.h>
 
 #include <algorithm>
@@ -84,7 +74,8 @@
 namespace {
 
 // The maximum supported versions for a given interface.
-// The version bound will be calculated using wl::CalcBindVersion().
+// The version bound will be the minimum of the value and the version
+// advertised by the server.
 constexpr uint32_t kMaxCompositorVersion = 4;
 constexpr uint32_t kMaxKeyboardExtensionVersion = 2;
 constexpr uint32_t kMaxXdgShellVersion = 5;
@@ -443,9 +434,7 @@
   } else if (!connection->compositor_ &&
              strcmp(interface, "wl_compositor") == 0) {
     connection->compositor_ = wl::Bind<wl_compositor>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxCompositorVersion,
-                                 wl_compositor_interface.version));
+        registry, name, std::min(version, kMaxCompositorVersion));
     connection->compositor_version_ = version;
     if (!connection->compositor_) {
       LOG(ERROR) << "Failed to bind to wl_compositor global";
@@ -460,9 +449,7 @@
     }
   } else if (!connection->shell_ && strcmp(interface, "xdg_wm_base") == 0) {
     connection->shell_ = wl::Bind<xdg_wm_base>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxXdgShellVersion,
-                                 xdg_wm_base_interface.version));
+        registry, name, std::min(version, kMaxXdgShellVersion));
     if (!connection->shell_) {
       LOG(ERROR) << "Failed to bind to xdg_wm_base global";
       return;
@@ -473,9 +460,7 @@
   } else if (!connection->alpha_compositing_ &&
              (strcmp(interface, "zcr_alpha_compositing_v1") == 0)) {
     connection->alpha_compositing_ = wl::Bind<zcr_alpha_compositing_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxAlphaCompositingVersion,
-                                 zcr_alpha_compositing_v1_interface.version));
+        registry, name, std::min(version, kMaxAlphaCompositingVersion));
     if (!connection->alpha_compositing_) {
       LOG(ERROR) << "Failed to bind zcr_alpha_compositing_v1";
       return;
@@ -485,10 +470,7 @@
               0)) {
     connection->linux_explicit_synchronization_ =
         wl::Bind<zwp_linux_explicit_synchronization_v1>(
-            registry, name,
-            wl::CalculateBindVersion(
-                version, kMaxExplicitSyncVersion,
-                zwp_linux_explicit_synchronization_v1_interface.version));
+            registry, name, std::min(version, kMaxExplicitSyncVersion));
     if (!connection->linux_explicit_synchronization_) {
       LOG(ERROR) << "Failed to bind zwp_linux_explicit_synchronization_v1";
       return;
@@ -496,9 +478,7 @@
   } else if (!connection->content_type_manager_v1_ &&
              (strcmp(interface, "wp_content_type_manager_v1") == 0)) {
     connection->content_type_manager_v1_ = wl::Bind<wp_content_type_manager_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxWpContentTypeVersion,
-                                 wp_content_type_manager_v1_interface.version));
+        registry, name, std::min(version, kMaxWpContentTypeVersion));
     if (!connection->content_type_manager_v1_) {
       LOG(ERROR) << "Failed to bind wp_content_type_v1";
       return;
@@ -506,9 +486,7 @@
   } else if (!connection->presentation_ &&
              (strcmp(interface, "wp_presentation") == 0)) {
     connection->presentation_ = wl::Bind<wp_presentation>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxWpPresentationVersion,
-                                 wp_presentation_interface.version));
+        registry, name, std::min(version, kMaxWpPresentationVersion));
     if (!connection->presentation_) {
       LOG(ERROR) << "Failed to bind wp_presentation";
       return;
@@ -518,9 +496,7 @@
   } else if (!connection->viewporter_ &&
              (strcmp(interface, "wp_viewporter") == 0)) {
     connection->viewporter_ = wl::Bind<wp_viewporter>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxWpViewporterVersion,
-                                 wp_viewporter_interface.version));
+        registry, name, std::min(version, kMaxWpViewporterVersion));
     if (!connection->viewporter_) {
       LOG(ERROR) << "Failed to bind wp_viewporter";
       return;
@@ -528,9 +504,7 @@
   } else if (!connection->keyboard_extension_v1_ &&
              strcmp(interface, "zcr_keyboard_extension_v1") == 0) {
     connection->keyboard_extension_v1_ = wl::Bind<zcr_keyboard_extension_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxKeyboardExtensionVersion,
-                                 zcr_keyboard_extension_v1_interface.version));
+        registry, name, std::min(version, kMaxKeyboardExtensionVersion));
     if (!connection->keyboard_extension_v1_) {
       LOG(ERROR) << "Failed to bind zcr_keyboard_extension_v1";
       return;
@@ -545,9 +519,7 @@
     connection->keyboard_shortcuts_inhibit_manager_v1_ =
         wl::Bind<zwp_keyboard_shortcuts_inhibit_manager_v1>(
             registry, name,
-            wl::CalculateBindVersion(
-                version, kMaxKeyboardShortcutsInhibitManagerVersion,
-                zwp_keyboard_shortcuts_inhibit_manager_v1_interface.version));
+            std::min(version, kMaxKeyboardShortcutsInhibitManagerVersion));
     if (!connection->keyboard_shortcuts_inhibit_manager_v1_) {
       LOG(ERROR) << "Failed to bind zwp_keyboard_shortcuts_inhibit_manager_v1";
       return;
@@ -555,9 +527,7 @@
   } else if (!connection->text_input_manager_v1_ &&
              strcmp(interface, "zwp_text_input_manager_v1") == 0) {
     connection->text_input_manager_v1_ = wl::Bind<zwp_text_input_manager_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxTextInputManagerVersion,
-                                 zwp_text_input_manager_v1_interface.version));
+        registry, name, std::min(version, kMaxTextInputManagerVersion));
     if (!connection->text_input_manager_v1_) {
       LOG(ERROR) << "Failed to bind to zwp_text_input_manager_v1 global";
       return;
@@ -566,18 +536,12 @@
              strcmp(interface, "zcr_text_input_extension_v1") == 0) {
     connection->text_input_extension_v1_ =
         wl::Bind<zcr_text_input_extension_v1>(
-            registry, name,
-            wl::CalculateBindVersion(
-                version, kMaxTextInputExtensionVersion,
-                zcr_text_input_extension_v1_interface.version));
+            registry, name, std::min(version, kMaxTextInputExtensionVersion));
   } else if (!connection->xdg_decoration_manager_ &&
              strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
     connection->xdg_decoration_manager_ =
         wl::Bind<struct zxdg_decoration_manager_v1>(
-            registry, name,
-            wl::CalculateBindVersion(
-                version, kMaxXdgDecorationVersion,
-                zxdg_decoration_manager_v1_interface.version));
+            registry, name, std::min(version, kMaxXdgDecorationVersion));
     if (!connection->xdg_decoration_manager_) {
       LOG(ERROR) << "Failed to bind zxdg_decoration_manager_v1";
       return;
@@ -585,9 +549,7 @@
   } else if (!connection->extended_drag_v1_ &&
              strcmp(interface, "zcr_extended_drag_v1") == 0) {
     connection->extended_drag_v1_ = wl::Bind<zcr_extended_drag_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxExtendedDragVersion,
-                                 zcr_extended_drag_v1_interface.version));
+        registry, name, std::min(version, kMaxExtendedDragVersion));
     if (!connection->extended_drag_v1_) {
       LOG(ERROR) << "Failed to bind to zcr_extended_drag_v1 global";
       return;
@@ -595,9 +557,7 @@
   } else if (!connection->xdg_output_manager_ &&
              strcmp(interface, "zxdg_output_manager_v1") == 0) {
     connection->xdg_output_manager_ = wl::Bind<struct zxdg_output_manager_v1>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxXdgOutputManagerVersion,
-                                 zxdg_output_manager_v1_interface.version));
+        registry, name, std::min(version, kMaxXdgOutputManagerVersion));
     if (!connection->xdg_output_manager_) {
       LOG(ERROR) << "Failed to bind zxdg_output_manager_v1";
       return;
@@ -615,9 +575,7 @@
   } else if (!connection->zcr_stylus_v2_ &&
              strcmp(interface, "zcr_stylus_v2") == 0) {
     connection->zcr_stylus_v2_ = wl::Bind<zcr_stylus_v2>(
-        registry, name,
-        wl::CalculateBindVersion(version, kMaxStylusVersion,
-                                 zcr_stylus_v2_interface.version));
+        registry, name, std::min(version, kMaxStylusVersion));
     if (!connection->zcr_stylus_v2_) {
       LOG(ERROR) << "Failed to bind to zcr_stylus_v2";
       return;
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc b/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc
index 9c4a5d5..a4cbec2f 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc
@@ -37,9 +37,7 @@
   }
 
   auto data_device_manager = wl::Bind<wl_data_device_manager>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               wl_data_device_manager_interface.version));
+      registry, name, std::min(version, kMaxVersion));
   if (!data_device_manager) {
     LOG(ERROR) << "Failed to bind to wl_data_device_manager global";
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index afb4371..e59d4ee 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -1097,4 +1097,10 @@
                          WaylandDataDragControllerTest,
                          Values(wl::ServerConfig{}));
 
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandDataDragControllerTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_output.cc b/ui/ozone/platform/wayland/host/wayland_output.cc
index 715a1da7..274bb5fe 100644
--- a/ui/ozone/platform/wayland/host/wayland_output.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output.cc
@@ -42,10 +42,8 @@
     return;
   }
 
-  auto output = wl::Bind<wl_output>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               wl_output_interface.version));
+  auto output =
+      wl::Bind<wl_output>(registry, name, std::min(version, kMaxVersion));
   if (!output) {
     LOG(ERROR) << "Failed to bind to wl_output global";
     return;
@@ -118,14 +116,8 @@
   DCHECK(!delegate_);
   delegate_ = delegate;
   static constexpr wl_output_listener output_listener = {
-      &OutputHandleGeometry,    &OutputHandleMode,
-      &OutputHandleDone,        &OutputHandleScale,
-#ifdef WL_OUTPUT_NAME_SINCE_VERSION
-      &OutputHandleName,
-#endif
-#ifdef WL_OUTPUT_DESCRIPTION_SINCE_VERSION
-      &OutputHandleDescription,
-#endif
+      &OutputHandleGeometry, &OutputHandleMode, &OutputHandleDone,
+      &OutputHandleScale,    &OutputHandleName, &OutputHandleDescription,
 
   };
   wl_output_add_listener(output_.get(), &output_listener, this);
@@ -266,7 +258,6 @@
     wayland_output->scale_factor_ = factor;
 }
 
-#ifdef WL_OUTPUT_NAME_SINCE_VERSION
 // static
 void WaylandOutput::OutputHandleName(void* data,
                                      struct wl_output* wl_output,
@@ -274,9 +265,7 @@
   if (WaylandOutput* wayland_output = static_cast<WaylandOutput*>(data))
     wayland_output->name_ = name ? std::string(name) : std::string{};
 }
-#endif
 
-#ifdef WL_OUTPUT_DESCRIPTION_SINCE_VERSION
 // static
 void WaylandOutput::OutputHandleDescription(void* data,
                                             struct wl_output* wl_output,
@@ -286,6 +275,5 @@
         description ? std::string(description) : std::string{};
   }
 }
-#endif
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_output.h b/ui/ozone/platform/wayland/host/wayland_output.h
index 8f052fa..e1b1bee 100644
--- a/ui/ozone/platform/wayland/host/wayland_output.h
+++ b/ui/ozone/platform/wayland/host/wayland_output.h
@@ -154,16 +154,12 @@
   static void OutputHandleScale(void* data,
                                 struct wl_output* wl_output,
                                 int32_t factor);
-#ifdef WL_OUTPUT_NAME_SINCE_VERSION
   static void OutputHandleName(void* data,
                                struct wl_output* wl_output,
                                const char* name);
-#endif
-#ifdef WL_OUTPUT_DESCRIPTION_SINCE_VERSION
   static void OutputHandleDescription(void* data,
                                       struct wl_output* wl_output,
                                       const char* description);
-#endif
 
   const Id output_id_ = 0;
   wl::Object<wl_output> output_;
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.cc b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
index 88bf0241..843fa74 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
@@ -156,7 +156,7 @@
   // received for their root surface) and |output_id| is the primary output.
   const bool is_primary =
       wayland_screen_ &&
-      metrics.output_id == wayland_screen_->GetPrimaryDisplay().id();
+      metrics.display_id == wayland_screen_->GetPrimaryDisplay().id();
   for (auto* window : connection_->wayland_window_manager()->GetAllWindows()) {
     auto entered_output = window->GetPreferredEnteredOutputId();
     if (entered_output == metrics.output_id || (!entered_output && is_primary))
diff --git a/ui/ozone/platform/wayland/host/wayland_output_unittest.cc b/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
index 2c6506b8..74e7250 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
@@ -59,4 +59,10 @@
                          WaylandOutputTest,
                          Values(wl::ServerConfig{}));
 
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandOutputTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_pointer.cc b/ui/ozone/platform/wayland/host/wayland_pointer.cc
index c827148..ba9832cd 100644
--- a/ui/ozone/platform/wayland/host/wayland_pointer.cc
+++ b/ui/ozone/platform/wayland/host/wayland_pointer.cc
@@ -37,11 +37,8 @@
                                Delegate* delegate)
     : obj_(pointer), connection_(connection), delegate_(delegate) {
   static constexpr wl_pointer_listener listener = {
-      &Enter,        &Leave,      &Motion,   &Button,       &Axis,
-      &Frame,        &AxisSource, &AxisStop, &AxisDiscrete,
-#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION
-      &AxisValue120,
-#endif
+      &Enter, &Leave,      &Motion,   &Button,       &Axis,
+      &Frame, &AxisSource, &AxisStop, &AxisDiscrete, &AxisValue120,
   };
 
   wl_pointer_add_listener(obj_.get(), &listener, this);
@@ -225,7 +222,6 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
-#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION
 // --- Version 8 ---
 
 // static
@@ -237,7 +233,6 @@
   // events.
   NOTIMPLEMENTED_LOG_ONCE();
 }
-#endif
 
 void WaylandPointer::SetupStylus() {
   auto* stylus_v2 = connection_->stylus_v2();
diff --git a/ui/ozone/platform/wayland/host/wayland_pointer.h b/ui/ozone/platform/wayland/host/wayland_pointer.h
index ec80f29..9448d215 100644
--- a/ui/ozone/platform/wayland/host/wayland_pointer.h
+++ b/ui/ozone/platform/wayland/host/wayland_pointer.h
@@ -82,12 +82,10 @@
                            wl_pointer* obj,
                            uint32_t axis,
                            int32_t discrete);
-#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION
   static void AxisValue120(void* data,
                            wl_pointer* obj,
                            uint32_t axis,
                            int32_t value120);
-#endif
 
   void SetupStylus();
 
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index ccb8aaf..43c9c54 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -159,7 +159,7 @@
       }
     }
   }
-  auto it = display_list_.FindDisplayById(output_id);
+  auto it = display_list_.FindDisplayById(display_id);
   if (it != display_list_.displays().end())
     display_list_.RemoveDisplay(display_id);
 }
diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
index 4bfbb77a5..0a1bb5a 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
@@ -1312,9 +1312,17 @@
                          WaylandScreenTest,
                          Values(wl::ServerConfig{}));
 
-INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
-                         WaylandAuraShellScreenTest,
-                         Values(wl::ServerConfig{}));
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandScreenTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTest,
+    WaylandAuraShellScreenTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          LazilyConfiguredScreenTest,
diff --git a/ui/ozone/platform/wayland/host/wayland_seat.cc b/ui/ozone/platform/wayland/host/wayland_seat.cc
index f4b7bcb..dd926ff 100644
--- a/ui/ozone/platform/wayland/host/wayland_seat.cc
+++ b/ui/ozone/platform/wayland/host/wayland_seat.cc
@@ -38,10 +38,8 @@
     return;
   }
 
-  auto seat = wl::Bind<struct wl_seat>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               wl_seat_interface.version));
+  auto seat =
+      wl::Bind<struct wl_seat>(registry, name, std::min(version, kMaxVersion));
   if (!seat) {
     LOG(ERROR) << "Failed to bind to wl_seat global";
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc b/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
index 9577ab0..8c7f9c90 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
@@ -62,5 +62,11 @@
                          WaylandSurfaceTest,
                          ::testing::Values(wl::ServerConfig{}));
 
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandSurfaceTest,
+    ::testing::Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
+
 }  // namespace
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_touch.cc b/ui/ozone/platform/wayland/host/wayland_touch.cc
index b3b3664..503ef906 100644
--- a/ui/ozone/platform/wayland/host/wayland_touch.cc
+++ b/ui/ozone/platform/wayland/host/wayland_touch.cc
@@ -42,13 +42,7 @@
                            Delegate* delegate)
     : obj_(touch), connection_(connection), delegate_(delegate) {
   static constexpr wl_touch_listener listener = {
-      &Down,        &Up, &Motion, &Frame, &Cancel,
-#ifdef WL_TOUCH_SHAPE_SINCE_VERSION
-      &Shape,
-#endif
-#ifdef WL_TOUCH_ORIENTATION_SINCE_VERSION
-      &Orientation,
-#endif
+      &Down, &Up, &Motion, &Frame, &Cancel, &Shape, &Orientation,
   };
 
   wl_touch_add_listener(obj_.get(), &listener, this);
@@ -122,7 +116,6 @@
                                        EventDispatchPolicyForPlatform());
 }
 
-#ifdef WL_TOUCH_SHAPE_SINCE_VERSION
 // static
 void WaylandTouch::Shape(void* data,
                          wl_touch* obj,
@@ -131,9 +124,7 @@
                          wl_fixed_t minor) {
   NOTIMPLEMENTED_LOG_ONCE();
 }
-#endif
 
-#ifdef WL_TOUCH_ORIENTATION_SINCE_VERSION
 // static
 void WaylandTouch::Orientation(void* data,
                                wl_touch* obj,
@@ -141,7 +132,6 @@
                                wl_fixed_t orientation) {
   NOTIMPLEMENTED_LOG_ONCE();
 }
-#endif
 
 // static
 void WaylandTouch::Cancel(void* data, wl_touch* obj) {
diff --git a/ui/ozone/platform/wayland/host/wayland_touch.h b/ui/ozone/platform/wayland/host/wayland_touch.h
index fda7076..4b8c4b0a 100644
--- a/ui/ozone/platform/wayland/host/wayland_touch.h
+++ b/ui/ozone/platform/wayland/host/wayland_touch.h
@@ -59,19 +59,15 @@
                      int32_t id,
                      wl_fixed_t x,
                      wl_fixed_t y);
-#ifdef WL_TOUCH_SHAPE_SINCE_VERSION
   static void Shape(void* data,
                     wl_touch* obj,
                     int32_t id,
                     wl_fixed_t major,
                     wl_fixed_t minor);
-#endif
-#ifdef WL_TOUCH_ORIENTATION_SINCE_VERSION
   static void Orientation(void* data,
                           wl_touch* obj,
                           int32_t id,
                           wl_fixed_t orientation);
-#endif
   static void Cancel(void* data, wl_touch* obj);
   static void Frame(void* data, wl_touch* obj);
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index bdc1056..06223e2 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -1395,4 +1395,10 @@
                          WaylandWindowDragControllerTest,
                          Values(wl::ServerConfig{}));
 
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandWindowDragControllerTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 295dd1d..0d3dd33 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -4270,9 +4270,18 @@
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowTest,
                          Values(wl::ServerConfig{}));
-
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandWindowTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandSubsurfaceTest,
                          Values(wl::ServerConfig{}));
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithAuraShell,
+    WaylandSubsurfaceTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
index 2c6afbf..7c2676d 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/wayland/wayland_display_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_output.h"
 #include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
@@ -105,7 +106,9 @@
       static_cast<int64_t>(std::numeric_limits<int32_t>::min()) - 1,
       std::numeric_limits<int32_t>::min(),
       std::numeric_limits<int32_t>::min() + 1,
+      -1,
       0,
+      1,
       std::numeric_limits<int32_t>::max() - 1,
       std::numeric_limits<int32_t>::max(),
       static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1,
@@ -113,17 +116,18 @@
       std::numeric_limits<int64_t>::max()};
 
   for (int64_t id : kTestIds) {
-    uint32_t display_id_hi = static_cast<uint32_t>(id >> 32);
-    uint32_t display_id_lo = static_cast<uint32_t>(id);
+    auto display_id = ui::wayland::ToWaylandDisplayIdPair(id);
     WaylandZAuraOutput aura_output;
-    WaylandZAuraOutput::OnDisplayId(&aura_output, nullptr, display_id_hi,
-                                    display_id_lo);
+    WaylandZAuraOutput::OnDisplayId(&aura_output, nullptr, display_id.high,
+                                    display_id.low);
     EXPECT_EQ(id, aura_output.display_id().value());
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
-                         WaylandZAuraOutputTest,
-                         Values(wl::ServerConfig{}));
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTest,
+    WaylandZAuraOutputTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
index 6b8dafc..05481b1 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
@@ -41,9 +41,7 @@
   }
 
   auto zaura_shell = wl::Bind<struct zaura_shell>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               zaura_shell_interface.version));
+      registry, name, std::min(version, kMaxVersion));
   if (!zaura_shell) {
     LOG(ERROR) << "Failed to bind zaura_shell";
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
index 625d94af..96498dfbc 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell_unittest.cc
@@ -36,8 +36,10 @@
   ASSERT_FALSE(connection_->zaura_shell()->HasBugFix(2));
 }
 
-INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
-                         WaylandZAuraShellTest,
-                         Values(wl::ServerConfig{}));
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTest,
+    WaylandZAuraShellTest,
+    Values(wl::ServerConfig{
+        .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zcr_color_manager.cc b/ui/ozone/platform/wayland/host/wayland_zcr_color_manager.cc
index 22e0e3be..9b30b7d9b 100644
--- a/ui/ozone/platform/wayland/host/wayland_zcr_color_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zcr_color_manager.cc
@@ -40,9 +40,7 @@
     return;
 
   auto color_manager = wl::Bind<struct zcr_color_manager_v1>(
-      registry, name,
-      wl::CalculateBindVersion(kMinVersion, kMaxVersion,
-                               zcr_color_manager_v1_interface.version));
+      registry, name, std::min(kMinVersion, kMaxVersion));
   if (!color_manager) {
     LOG(ERROR) << "Failed to bind zcr_color_manager_v1";
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.cc b/ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.cc
index 47f7939..3db8cb5 100644
--- a/ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.cc
@@ -38,9 +38,7 @@
   }
 
   auto zwp_linux_dmabuf = wl::Bind<zwp_linux_dmabuf_v1>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               zwp_linux_dmabuf_v1_interface.version));
+      registry, name, std::min(version, kMaxVersion));
   if (!zwp_linux_dmabuf) {
     LOG(ERROR) << "Failed to bind zwp_linux_dmabuf_v1";
     return;
diff --git a/ui/ozone/platform/wayland/host/xdg_activation.cc b/ui/ozone/platform/wayland/host/xdg_activation.cc
index 765898a..3385758 100644
--- a/ui/ozone/platform/wayland/host/xdg_activation.cc
+++ b/ui/ozone/platform/wayland/host/xdg_activation.cc
@@ -62,10 +62,8 @@
   if (connection->xdg_activation_)
     return;
 
-  auto instance = wl::Bind<::xdg_activation_v1>(
-      registry, name,
-      wl::CalculateBindVersion(version, kMaxVersion,
-                               xdg_activation_v1_interface.version));
+  auto instance = wl::Bind<::xdg_activation_v1>(registry, name,
+                                                std::min(version, kMaxVersion));
   if (!instance) {
     LOG(ERROR) << "Failed to bind " << kInterfaceName;
     return;
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
index e604780..9116980a 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
@@ -98,16 +98,12 @@
   }
 
   static constexpr xdg_toplevel_listener xdg_toplevel_listener = {
-    &ConfigureTopLevel,
-    &CloseTopLevel,
-#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
-    // Since v4
-    &ConfigureBounds,
-#endif
-#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
-    // Since v5
-    &WmCapabilities,
-#endif
+      &ConfigureTopLevel,
+      &CloseTopLevel,
+      // Since v4
+      &ConfigureBounds,
+      // Since v5
+      &WmCapabilities,
   };
 
   if (!xdg_surface_wrapper_)
@@ -324,7 +320,6 @@
   surface->wayland_window_->OnCloseRequest();
 }
 
-#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
 // static
 void XDGToplevelWrapperImpl::ConfigureBounds(void* data,
                                              struct xdg_toplevel* xdg_toplevel,
@@ -332,16 +327,13 @@
                                              int32_t height) {
   NOTIMPLEMENTED_LOG_ONCE();
 }
-#endif
 
-#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
 // static
 void XDGToplevelWrapperImpl::WmCapabilities(void* data,
                                             struct xdg_toplevel* xdg_toplevel,
                                             struct wl_array* capabilities) {
   NOTIMPLEMENTED_LOG_ONCE();
 }
-#endif
 
 void XDGToplevelWrapperImpl::SetTopLevelDecorationMode(
     DecorationMode requested_mode) {
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
index 03cbeba..a9a0bc1c 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
@@ -69,19 +69,13 @@
                                 int32_t height,
                                 struct wl_array* states);
   static void CloseTopLevel(void* data, struct xdg_toplevel* xdg_toplevel);
-
-#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
   static void ConfigureBounds(void* data,
                               struct xdg_toplevel* xdg_toplevel,
                               int32_t width,
                               int32_t height);
-#endif
-
-#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
   static void WmCapabilities(void* data,
                              struct xdg_toplevel* xdg_toplevel,
                              struct wl_array* capabilities);
-#endif
 
   // zxdg_decoration_listener
   static void ConfigureDecoration(
diff --git a/ui/ozone/platform/wayland/test/mock_zaura_shell.cc b/ui/ozone/platform/wayland/test/mock_zaura_shell.cc
index 0ff1a73e..2f3bdc0 100644
--- a/ui/ozone/platform/wayland/test/mock_zaura_shell.cc
+++ b/ui/ozone/platform/wayland/test/mock_zaura_shell.cc
@@ -16,8 +16,8 @@
 
 namespace {
 
-constexpr uint32_t kZAuraShellVersion = 42;
-constexpr uint32_t kZAuraOutputVersion = 38;
+constexpr uint32_t kZAuraShellVersion = 44;
+constexpr uint32_t kZAuraOutputVersion = 43;
 
 void GetAuraSurface(wl_client* client,
                     wl_resource* resource,
diff --git a/ui/ozone/platform/wayland/test/test_output.cc b/ui/ozone/platform/wayland/test/test_output.cc
index 981f0fd..77c9982 100644
--- a/ui/ozone/platform/wayland/test/test_output.cc
+++ b/ui/ozone/platform/wayland/test/test_output.cc
@@ -6,7 +6,9 @@
 
 #include <wayland-server-protocol.h>
 
+#include "base/check_op.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/display/types/display_constants.h"
 
 namespace wl {
 
@@ -39,9 +41,10 @@
 void TestOutput::Flush() {
   constexpr char kUnknownMake[] = "unknown_make";
   constexpr char kUnknownModel[] = "unknown_model";
-
-  if (!pending_rect_ && !pending_scale_)
+  if ((!pending_rect_ && !pending_scale_) ||
+      (aura_shell_enabled_ && !aura_output_)) {
     return;
+  }
 
   if (pending_rect_ || pending_transform_) {
     if (pending_rect_)
@@ -82,6 +85,9 @@
 
 void TestOutput::SetAuraOutput(TestZAuraOutput* aura_output) {
   aura_output_ = aura_output;
+  // Make sure to send the necessary information for a client that
+  // relies on the aura output information.
+  Flush();
 }
 
 TestZAuraOutput* TestOutput::GetAuraOutput() {
diff --git a/ui/ozone/platform/wayland/test/test_output.h b/ui/ozone/platform/wayland/test/test_output.h
index ff636c6f..0c138ec1 100644
--- a/ui/ozone/platform/wayland/test/test_output.h
+++ b/ui/ozone/platform/wayland/test/test_output.h
@@ -28,6 +28,10 @@
 
   static TestOutput* FromResource(wl_resource* resource);
 
+  // Useful only when zaura_shell is supported.
+  void set_aura_shell_enabled() { aura_shell_enabled_ = true; }
+  bool aura_shell_enabled() { return aura_shell_enabled_; }
+
   const gfx::Rect GetRect() { return rect_; }
   void SetRect(const gfx::Rect& rect);
   int32_t GetScale() const { return scale_; }
@@ -43,6 +47,7 @@
   void OnBind() override;
 
  private:
+  bool aura_shell_enabled_ = false;
   gfx::Rect rect_;
   int32_t scale_;
   wl_output_transform transform_{WL_OUTPUT_TRANSFORM_NORMAL};
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
index 5a43400..6039565 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
@@ -134,6 +134,7 @@
     return false;
   if (!alpha_compositing_.Initialize(display_.get()))
     return false;
+
   if (!output_.Initialize(display_.get()))
     return false;
   SetupOutputs();
@@ -148,8 +149,12 @@
 
   if (!xdg_shell_.Initialize(display_.get()))
     return false;
-  if (!zaura_shell_.Initialize(display_.get()))
-    return false;
+
+  if (config.enable_aura_shell == EnableAuraShellProtocol::kEnabled) {
+    output_.set_aura_shell_enabled();
+    if (!zaura_shell_.Initialize(display_.get()))
+      return false;
+  }
 
   if (!zcr_stylus_.Initialize(display_.get()))
     return false;
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index 02e9e8ee..4cd36ed 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -16,6 +16,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_checker.h"
+#include "ui/display/types/display_constants.h"
 #include "ui/ozone/platform/wayland/test/global_object.h"
 #include "ui/ozone/platform/wayland/test/mock_wp_presentation.h"
 #include "ui/ozone/platform/wayland/test/mock_xdg_shell.h"
@@ -51,6 +52,7 @@
 enum class PrimarySelectionProtocol { kNone, kGtk, kZwp };
 enum class CompositorVersion { kV3, kV4 };
 enum class ShouldUseExplicitSynchronizationProtocol { kNone, kUse };
+enum class EnableAuraShellProtocol { kEnabled, kDisabled };
 
 struct ServerConfig {
   CompositorVersion compositor_version = CompositorVersion::kV4;
@@ -58,6 +60,8 @@
       PrimarySelectionProtocol::kNone;
   ShouldUseExplicitSynchronizationProtocol use_explicit_synchronization =
       ShouldUseExplicitSynchronizationProtocol::kUse;
+  EnableAuraShellProtocol enable_aura_shell =
+      EnableAuraShellProtocol::kDisabled;
 };
 
 class TestWaylandServerThread;
@@ -135,6 +139,8 @@
 
   TestOutput* CreateAndInitializeOutput() {
     auto output = std::make_unique<TestOutput>();
+    if (output_.aura_shell_enabled())
+      output->set_aura_shell_enabled();
     output->Initialize(display());
 
     TestOutput* output_ptr = output.get();
diff --git a/ui/ozone/platform/wayland/test/test_zaura_output.cc b/ui/ozone/platform/wayland/test/test_zaura_output.cc
index 7e9f364c..1195802 100644
--- a/ui/ozone/platform/wayland/test/test_zaura_output.cc
+++ b/ui/ozone/platform/wayland/test/test_zaura_output.cc
@@ -6,13 +6,28 @@
 
 #include <aura-shell-server-protocol.h>
 
+#include "ui/base/wayland/wayland_display_util.h"
+
 namespace wl {
+namespace {
+int64_t display_id_counter = 10;
+}
 
 TestZAuraOutput::TestZAuraOutput(wl_resource* resource)
-    : ServerObject(resource) {}
+    : ServerObject(resource), display_id_(display_id_counter++) {
+  if (wl_resource_get_version(resource) >=
+      ZAURA_OUTPUT_DISPLAY_ID_SINCE_VERSION) {
+    auto display_id = ui::wayland::ToWaylandDisplayIdPair(display_id_);
+    zaura_output_send_display_id(resource, display_id.high, display_id.low);
+  }
+}
 
 TestZAuraOutput::~TestZAuraOutput() = default;
 
+void TestZAuraOutput::SendActivated() {
+  zaura_output_send_activated(resource());
+}
+
 void TestZAuraOutput::Flush() {
   if (pending_insets_) {
     insets_ = std::move(*pending_insets_);
diff --git a/ui/ozone/platform/wayland/test/test_zaura_output.h b/ui/ozone/platform/wayland/test/test_zaura_output.h
index 41ee51d..77a60d89 100644
--- a/ui/ozone/platform/wayland/test/test_zaura_output.h
+++ b/ui/ozone/platform/wayland/test/test_zaura_output.h
@@ -23,6 +23,8 @@
 
   ~TestZAuraOutput() override;
 
+  int64_t display_id() const { return display_id_; }
+
   const gfx::Insets& GetInsets() const { return insets_; }
   void SetInsets(const gfx::Insets& insets) { pending_insets_ = insets; }
 
@@ -31,9 +33,13 @@
     pending_logical_transform_ = logical_transform;
   }
 
+  // Send display id, activated events, immediately.
+  void SendActivated();
+
   void Flush();
 
  private:
+  int64_t display_id_;
   gfx::Insets insets_;
   absl::optional<gfx::Insets> pending_insets_;
 
diff --git a/ui/ozone/platform/x11/test/x11_event_translation_unittest.cc b/ui/ozone/platform/x11/test/x11_event_translation_unittest.cc
index 5d133f45..f088919 100644
--- a/ui/ozone/platform/x11/test/x11_event_translation_unittest.cc
+++ b/ui/ozone/platform/x11/test/x11_event_translation_unittest.cc
@@ -238,4 +238,44 @@
   EXPECT_EQ(ET_KEY_RELEASED, keyev_shift_r_released->type());
 }
 
+// Verifies that scroll events remain ET_SCROLL type or are translated to
+// ET_SCROLL_FLING_START depending on their X and Y offsets.
+TEST(XEventTranslationTest, ScrollEventType) {
+  int device_id = 1;
+  ui::SetUpTouchPadForTest(device_id);
+
+  struct ScrollEventTestData {
+    int x_offset_;
+    int y_offset_;
+    int x_offset_ordinal_;
+    int y_offset_ordinal_;
+    EventType expectedEventType_;
+  };
+  const std::vector<ScrollEventTestData> test_data = {
+      // Ordinary horizontal scrolling remains ET_SCROLL.
+      {1, 0, 1, 0, EventType::ET_SCROLL},
+      // Ordinary vertical scrolling remains ET_SCROLL.
+      {0, 10, 0, 10, EventType::ET_SCROLL},
+      // Ordinary diagonal scrolling remains ET_SCROLL.
+      {47, -11, 47, -11, EventType::ET_SCROLL},
+      // If x_offset and y_offset both are 0, expected event type is
+      // ET_SCROLL_FLING_START and not ET_SCROLL.
+      {0, 0, 0, 0, EventType::ET_SCROLL_FLING_START}};
+
+  for (const auto& data : test_data) {
+    ui::ScopedXI2Event xev;
+    xev.InitScrollEvent(device_id, data.x_offset_, data.y_offset_,
+                        data.x_offset_ordinal_, data.y_offset_ordinal_, 2);
+
+    const auto event = BuildEventFromXEvent(*xev);
+    EXPECT_TRUE(event);
+    EXPECT_EQ(event->type(), data.expectedEventType_);
+
+    const ScrollEvent* scroll_event = static_cast<ScrollEvent*>(event.get());
+    EXPECT_EQ(scroll_event->x_offset(), data.x_offset_);
+    EXPECT_EQ(scroll_event->y_offset(), data.y_offset_);
+    EXPECT_EQ(scroll_event->x_offset_ordinal(), data.x_offset_ordinal_);
+    EXPECT_EQ(scroll_event->y_offset_ordinal(), data.y_offset_ordinal_);
+  }
+}
 }  // namespace ui
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index f7652b0..306423ef 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -153,11 +153,14 @@
 # Files for which .d.ts files will be auto-generated with ts_definitions().
 generate_definitions_js_files = [
   "js/assert.js",
-  "js/cr/event_target.js",
   "js/load_time_data.m.js",
   "js/util.js",
 ]
 
+if (is_chromeos_ash) {
+  generate_definitions_js_files += [ "js/cr/event_target.js" ]
+}
+
 if (is_ios) {
   generate_definitions_js_files += [ "js/ios/web_ui.js" ]
 }
diff --git a/ui/webui/resources/cr_components/app_management/BUILD.gn b/ui/webui/resources/cr_components/app_management/BUILD.gn
index 4b1d324c..59027bb 100644
--- a/ui/webui/resources/cr_components/app_management/BUILD.gn
+++ b/ui/webui/resources/cr_components/app_management/BUILD.gn
@@ -108,7 +108,7 @@
 
 copy("copy_mojo") {
   deps = [
-    ":mojo_bindings_webui_js",
+    ":mojo_bindings_js__generator",
     "//components/services/app_service/public/mojom:types_js__generator",
   ]
   sources = [
diff --git a/ui/webui/resources/cr_components/color_change_listener/BUILD.gn b/ui/webui/resources/cr_components/color_change_listener/BUILD.gn
index 6e07996..2b64012 100644
--- a/ui/webui/resources/cr_components/color_change_listener/BUILD.gn
+++ b/ui/webui/resources/cr_components/color_change_listener/BUILD.gn
@@ -17,7 +17,7 @@
 }
 
 copy("copy_mojom") {
-  deps = [ ":mojom_webui_js" ]
+  deps = [ ":mojom_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom-webui.js" ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
diff --git a/ui/webui/resources/cr_components/customize_themes/BUILD.gn b/ui/webui/resources/cr_components/customize_themes/BUILD.gn
index 4337173..085adeb 100644
--- a/ui/webui/resources/cr_components/customize_themes/BUILD.gn
+++ b/ui/webui/resources/cr_components/customize_themes/BUILD.gn
@@ -44,7 +44,7 @@
 }
 
 copy("copy_mojom") {
-  deps = [ ":mojom_webui_js" ]
+  deps = [ ":mojom_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/cr_components/customize_themes/customize_themes.mojom-webui.js" ]
   outputs = [ "$preprocess_folder_tmp/{{source_file_part}}" ]
 }
diff --git a/ui/webui/resources/cr_components/help_bubble/BUILD.gn b/ui/webui/resources/cr_components/help_bubble/BUILD.gn
index ef902a9..229a679 100644
--- a/ui/webui/resources/cr_components/help_bubble/BUILD.gn
+++ b/ui/webui/resources/cr_components/help_bubble/BUILD.gn
@@ -63,7 +63,7 @@
 }
 
 copy("copy_mojom") {
-  deps = [ ":mojo_bindings_webui_js" ]
+  deps = [ ":mojo_bindings_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/cr_components/help_bubble/help_bubble.mojom-webui.js" ]
   outputs = [ "$preprocess_folder_tmp/{{source_file_part}}" ]
 }
diff --git a/ui/webui/resources/cr_components/history_clusters/BUILD.gn b/ui/webui/resources/cr_components/history_clusters/BUILD.gn
index 42bf34e..9a820703 100644
--- a/ui/webui/resources/cr_components/history_clusters/BUILD.gn
+++ b/ui/webui/resources/cr_components/history_clusters/BUILD.gn
@@ -55,7 +55,7 @@
 copy("copy_history_clusters_mojom") {
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/cr_components/history_clusters/history_clusters.mojom-webui.js" ]
   outputs = [ "$preprocess_folder_tmp/{{source_file_part}}" ]
-  deps = [ ":mojo_bindings_webui_js" ]
+  deps = [ ":mojo_bindings_js__generator" ]
 }
 
 ts_library("build_ts") {
diff --git a/ui/webui/resources/cr_components/most_visited/BUILD.gn b/ui/webui/resources/cr_components/most_visited/BUILD.gn
index 82ea292..aec4c74 100644
--- a/ui/webui/resources/cr_components/most_visited/BUILD.gn
+++ b/ui/webui/resources/cr_components/most_visited/BUILD.gn
@@ -30,7 +30,7 @@
 }
 
 copy("copy_mojom") {
-  deps = [ ":mojom_webui_js" ]
+  deps = [ ":mojom_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/cr_components/most_visited/most_visited.mojom-webui.js" ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index 8bdf393..53fd990 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -81,13 +81,21 @@
   out_manifest = "$target_gen_dir/$preprocess_src_manifest"
   in_files = [
     "assert.js",
-    "cr.m.js",
-    "cr/event_target.js",
     "load_time_data.m.js",
     "load_time_data_deprecated.js",
     "util.js",
   ]
 
+  if (is_chromeos_ash) {
+    # Used by ChromeOS Closure UIs.
+    in_files += [ "cr/event_target.js" ]
+  }
+
+  if (is_chromeos_ash || is_android) {
+    # Used by ChromeOS and Android Closure UIs.
+    in_files += [ "cr.m.js" ]
+  }
+
   if (is_chromeos_ash || is_ios) {
     # Used by ChromeOS UIs and ios inspect and omaha UIs
     in_files += [ "util_deprecated.js" ]
@@ -129,26 +137,32 @@
 # Targets for type-checking JS Modules
 
 group("closure_compile_modules") {
-  deps = [
-    ":js_resources_modules",
-    "cr:closure_compile_modules",
-  ]
+  deps = [ ":js_resources_modules" ]
+
+  if (is_chromeos_ash) {
+    deps += [ "cr:closure_compile_modules" ]
+  }
 }
 
 js_type_check("js_resources_modules") {
   is_polymer3 = true
   deps = [
     ":assert",
-    ":cr.m",
     ":load_time_data.m",
     ":util",
   ]
+
+  if (is_chromeos_ash || is_android) {
+    deps += [ ":cr.m" ]
+  }
 }
 
 js_library("assert") {
 }
 
-js_library("cr.m") {
+if (is_chromeos_ash || is_android) {
+  js_library("cr.m") {
+  }
 }
 
 js_library("load_time_data.m") {
diff --git a/ui/webui/resources/js/browser_command/BUILD.gn b/ui/webui/resources/js/browser_command/BUILD.gn
index 26de106f..016e0a2c 100644
--- a/ui/webui/resources/js/browser_command/BUILD.gn
+++ b/ui/webui/resources/js/browser_command/BUILD.gn
@@ -37,7 +37,7 @@
 }
 
 copy("copy_mojom") {
-  deps = [ ":mojo_bindings_webui_js" ]
+  deps = [ ":mojo_bindings_js__generator" ]
   sources = [ "$root_gen_dir/mojom-webui/ui/webui/resources/js/browser_command/browser_command.mojom-webui.js" ]
   outputs = [ "$tmp_folder/{{source_file_part}}" ]
 }
diff --git a/ui/webui/resources/js/cr/BUILD.gn b/ui/webui/resources/js/cr/BUILD.gn
index 36515aa..083e9c6 100644
--- a/ui/webui/resources/js/cr/BUILD.gn
+++ b/ui/webui/resources/js/cr/BUILD.gn
@@ -2,8 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/ui_mode.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 
+assert(is_chromeos_ash)
+
 js_type_check("closure_compile_modules") {
   deps = [ ":event_target" ]
 }
diff --git a/ui/webui/resources/js/metrics_reporter/BUILD.gn b/ui/webui/resources/js/metrics_reporter/BUILD.gn
index 775d617..6fcb00b 100644
--- a/ui/webui/resources/js/metrics_reporter/BUILD.gn
+++ b/ui/webui/resources/js/metrics_reporter/BUILD.gn
@@ -35,7 +35,7 @@
 }
 
 copy("copy_src_and_mojom") {
-  deps = [ ":mojo_bindings_webui_js" ]
+  deps = [ ":mojo_bindings_js__generator" ]
   sources = [
     "$root_gen_dir/mojom-webui/ui/webui/resources/js/metrics_reporter/metrics_reporter.mojom-webui.js",
     "browser_proxy.ts",
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index a60f19d..9d834b7 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -946,7 +946,7 @@
     "grit/weblayer_resources.h",
     "weblayer_resources.pak",
   ]
-  deps = [ "//weblayer/browser/webui:mojo_bindings_webui_js" ]
+  deps = [ "//weblayer/browser/webui:mojo_bindings_js__generator" ]
 }
 # TODO(jam): move weblayer_shell_resources_grit and copy_shell_resources here in
 # a way that's shareable?
diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn
index c2b910d..3dce88e 100644
--- a/weblayer/browser/android/javatests/BUILD.gn
+++ b/weblayer/browser/android/javatests/BUILD.gn
@@ -8,10 +8,7 @@
 
 android_library("webengine_java_tests") {
   testonly = true
-  sources = [
-    "src/org/chromium/webengine/test/ExecuteScriptTest.java",
-    "src/org/chromium/webengine/test/WebFragmentTest.java",
-  ]
+  sources = [ "src/org/chromium/webengine/test/WebFragmentTest.java" ]
   deps = [
     ":webengine_java_test_support",
     "//base:base_java",
@@ -20,7 +17,6 @@
     "//components/safe_browsing/android:safe_browsing_java",
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
-    "//net/android:net_java_test_support",
     "//third_party/android_deps:com_google_guava_listenablefuture_java",
     "//third_party/android_deps:guava_android_java",
     "//third_party/android_support_test_runner:rules_java",
@@ -30,7 +26,6 @@
     "//third_party/androidx:androidx_appcompat_appcompat_java",
     "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
-    "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/blink/public/common:common_java",
     "//third_party/junit:junit",
@@ -52,10 +47,8 @@
     "//base:base_java_test_support",
     "//content/public/test/android:content_java_test_support",
     "//net/android:net_java_test_support",
-    "//third_party/android_deps:com_google_guava_listenablefuture_java",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
-    "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
     "//third_party/junit:junit",
     "//ui/android:ui_java_test_support",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/ExecuteScriptTest.java b/weblayer/browser/android/javatests/src/org/chromium/webengine/test/ExecuteScriptTest.java
deleted file mode 100644
index 574de93..0000000
--- a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/ExecuteScriptTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.webengine.test;
-
-import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
-
-import android.content.pm.PackageManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.PackageUtils;
-import org.chromium.base.test.util.Batch;
-import org.chromium.net.test.util.TestWebServer;
-import org.chromium.webengine.RestrictedAPIException;
-import org.chromium.webengine.Tab;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Tests executing JavaScript in a Tab.
- */
-@Batch(Batch.PER_CLASS)
-@RunWith(WebEngineJUnit4ClassRunner.class)
-public class ExecuteScriptTest {
-    @Rule
-    public InstrumentationActivityTestRule mActivityTestRule =
-            new InstrumentationActivityTestRule();
-
-    private static final String ASSETLINKS_PATH = "/.well-known/assetlinks.json";
-    // TODO(crbug.com/1376522): Figure out how to not hardcode a port number.
-    private static final int PORT = 8888;
-
-    private TestWebServer mServer;
-    private Tab mTab;
-    private String mDefaultUrl;
-
-    @Before
-    public void setUp() throws Exception {
-        mServer = TestWebServer.start(PORT);
-        mActivityTestRule.launchShell();
-
-        mDefaultUrl = mServer.setResponse("/page.html",
-                "<html><head></head><body>contents!</body><script>window.foo = 42;</script></html>",
-                null);
-        // By default, the asset links are not set up.
-        mServer.setResponseWithNotFoundStatus(ASSETLINKS_PATH, null);
-    }
-
-    @After
-    public void tearDown() {
-        mServer.shutdown();
-        mActivityTestRule.finish();
-    }
-
-    private Tab navigate() throws Exception {
-        mActivityTestRule.attachNewFragmentThenNavigateAndWait(mDefaultUrl);
-        return mActivityTestRule.getFragment().getTabManager().get().getActiveTab().get();
-    }
-
-    private static String makeAssetFile(String packageName, String fingerprint) {
-        try {
-            return (new JSONArray().put(
-                            new JSONObject()
-                                    .put("relation",
-                                            new JSONArray().put(
-                                                    "delegate_permission/common.handle_all_urls"))
-                                    .put("target",
-                                            new JSONObject()
-                                                    .put("namespace", "android_app")
-                                                    .put("package_name", packageName)
-                                                    .put("sha256_cert_fingerprints",
-                                                            new JSONArray().put(fingerprint)))))
-                    .toString();
-        } catch (JSONException e) {
-        }
-        return "";
-    }
-
-    private void setUpDigitalAssetLinks() {
-        String packageName = ContextUtils.getApplicationContext().getPackageName();
-        PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
-        List<String> signatureFingerprints =
-                PackageUtils.getCertificateSHA256FingerprintForPackage(packageManager, packageName);
-        mServer.setResponse(
-                ASSETLINKS_PATH, makeAssetFile(packageName, signatureFingerprints.get(0)), null);
-    }
-
-    @Test
-    @SmallTest
-    public void executeScriptFailsWithoutVerification() throws Exception {
-        Tab activeTab = navigate();
-
-        CountDownLatch executeLatch = new CountDownLatch(1);
-
-        ListenableFuture<String> future =
-                runOnUiThreadBlocking(() -> activeTab.executeScript("1+1", true));
-
-        Futures.addCallback(future, new FutureCallback<String>() {
-            @Override
-            public void onSuccess(String result) {
-                Assert.fail("future resolved unexpectedly.");
-            }
-
-            @Override
-            public void onFailure(Throwable thrown) {
-                if (!(thrown instanceof RestrictedAPIException)) {
-                    Assert.fail(
-                            "expected future to fail due to RestrictedAPIException, instead got: "
-                            + thrown.getClass().getName());
-                }
-                executeLatch.countDown();
-            }
-        }, mActivityTestRule.getContext().getMainExecutor());
-
-        executeLatch.await();
-    }
-
-    @Test
-    @SmallTest
-    public void executeScriptSucceedsWithVerification() throws Exception {
-        setUpDigitalAssetLinks();
-        Tab activeTab = navigate();
-
-        ListenableFuture<String> future =
-                runOnUiThreadBlocking(() -> activeTab.executeScript("1+1", true));
-        Assert.assertEquals(future.get(), "2");
-    }
-
-    @Test
-    @SmallTest
-    public void useSeparateIsolate() throws Exception {
-        setUpDigitalAssetLinks();
-        Tab activeTab = navigate();
-
-        ListenableFuture<String> futureIsolated =
-                runOnUiThreadBlocking(() -> activeTab.executeScript("window.foo", true));
-        Assert.assertEquals(futureIsolated.get(), "null");
-
-        ListenableFuture<String> futureUnisolated =
-                runOnUiThreadBlocking(() -> activeTab.executeScript("window.foo", false));
-        Assert.assertEquals(futureUnisolated.get(), "42");
-    }
-
-    @Test
-    @SmallTest
-    public void modifyDOMSucceeds() throws Exception {
-        setUpDigitalAssetLinks();
-        Tab activeTab = navigate();
-
-        ListenableFuture<String> future1 = runOnUiThreadBlocking(
-                () -> activeTab.executeScript("document.body.innerText", false));
-        Assert.assertEquals(future1.get(), "\"contents!\"");
-
-        runOnUiThreadBlocking(
-                () -> activeTab.executeScript("document.body.innerText = 'foo'", false))
-                .get();
-
-        ListenableFuture<String> future2 = runOnUiThreadBlocking(
-                () -> activeTab.executeScript("document.body.innerText", false));
-        Assert.assertEquals(future2.get(), "\"foo\"");
-    }
-}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/InstrumentationActivityTestRule.java b/weblayer/browser/android/javatests/src/org/chromium/webengine/test/InstrumentationActivityTestRule.java
index c3898de6..7294e2a 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/InstrumentationActivityTestRule.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/webengine/test/InstrumentationActivityTestRule.java
@@ -4,28 +4,16 @@
 
 package org.chromium.webengine.test;
 
-import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
-
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.Nullable;
+import org.junit.Rule;
 
-import org.junit.Assert;
-
-import org.chromium.webengine.Navigation;
-import org.chromium.webengine.NavigationObserver;
-import org.chromium.webengine.Tab;
-import org.chromium.webengine.TabManager;
-import org.chromium.webengine.WebFragment;
-import org.chromium.webengine.WebSandbox;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.EmbeddedTestServerRule;
 import org.chromium.webengine.shell.InstrumentationActivity;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 /**
  * ActivityTestRule for InstrumentationActivity.
  *
@@ -33,8 +21,8 @@
  */
 public class InstrumentationActivityTestRule
         extends WebEngineActivityTestRule<InstrumentationActivity> {
-    @Nullable
-    private WebFragment mFragment;
+    @Rule
+    private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
 
     public InstrumentationActivityTestRule() {
         super(InstrumentationActivity.class);
@@ -43,7 +31,7 @@
     /**
      * Starts the WebEngine Instrumentation activity.
      */
-    public void launchShell() {
+    public InstrumentationActivity launchShell() {
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -51,86 +39,14 @@
                 new ComponentName(InstrumentationRegistry.getInstrumentation().getTargetContext(),
                         InstrumentationActivity.class));
         launchActivity(intent);
-    }
-
-    public void finish() {
-        Assert.assertNotNull(getActivity());
-        getActivity().finish();
-    }
-
-    public WebSandbox getWebSandbox() throws Exception {
-        return getActivity().getWebSandboxFuture().get();
-    }
-
-    /**
-     * Attaches a fragment to the container in the activity. If a fragment is already present, it
-     * will detach it first.
-     */
-    public void attachFragment(WebFragment fragment) {
-        if (mFragment != null) {
-            detachFragment(mFragment);
-        }
-
-        getActivity().attachFragment(fragment);
-        mFragment = fragment;
-    }
-
-    /**
-     * Return the current fragment attached to the fragment container in the activity.
-     */
-    public WebFragment getFragment() {
-        Assert.assertNotNull(mFragment);
-        return mFragment;
-    }
-
-    public void detachFragment(WebFragment fragment) {
-        getActivity().detachFragment(fragment);
-    }
-
-    public Tab getActiveTab() throws Exception {
-        return getFragment().getTabManager().get().getActiveTab().get();
-    }
-
-    /**
-     * Creates and attaches a new WebFragment before navigating.
-     */
-    public void attachNewFragmentThenNavigateAndWait(String path) throws Exception {
-        WebSandbox sandbox = getWebSandbox();
-        WebFragment fragment = runOnUiThreadBlocking(() -> sandbox.createFragment());
-        runOnUiThreadBlocking(() -> attachFragment(fragment));
-
-        TabManager tabManager = fragment.getTabManager().get();
-        Tab activeTab = tabManager.getActiveTab().get();
-
-        navigateAndWait(activeTab, path);
-    }
-
-    /**
-     * Navigates Tab to new path and waits for navigation to complete.
-     */
-    public void navigateAndWait(Tab tab, String url) throws Exception {
-        CountDownLatch navigationCompleteLatch = new CountDownLatch(1);
-        AtomicBoolean failed = new AtomicBoolean(false);
-        runOnUiThreadBlocking(() -> {
-            tab.getNavigationController().registerNavigationObserver(new NavigationObserver() {
-                @Override
-                public void onNavigationCompleted(Navigation navigation) {
-                    navigationCompleteLatch.countDown();
-                }
-                @Override
-                public void onNavigationFailed(Navigation navigation) {
-                    failed.set(true);
-                    navigationCompleteLatch.countDown();
-                }
-            });
-            tab.getNavigationController().navigate(url);
-        });
-        navigationCompleteLatch.await();
-
-        if (failed.get()) throw new RuntimeException("Navigation failed.");
-    }
-
-    public Context getContext() {
         return getActivity();
     }
+
+    public EmbeddedTestServer getTestServer() {
+        return mTestServerRule.getServer();
+    }
+
+    public String getTestDataURL(String path) {
+        return getTestServer().getURL("/weblayer/test/data/" + path);
+    }
 }
diff --git a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/WebFragmentTest.java b/weblayer/browser/android/javatests/src/org/chromium/webengine/test/WebFragmentTest.java
index 37923bee..26fe950 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/webengine/test/WebFragmentTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/webengine/test/WebFragmentTest.java
@@ -4,13 +4,10 @@
 
 package org.chromium.webengine.test;
 
-import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
-
-import android.net.Uri;
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlockingNoException;
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -18,106 +15,31 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.Batch;
-import org.chromium.net.test.EmbeddedTestServer;
-import org.chromium.net.test.EmbeddedTestServerRule;
-import org.chromium.webengine.Tab;
-import org.chromium.webengine.TabManager;
-import org.chromium.webengine.WebFragment;
 import org.chromium.webengine.WebSandbox;
+import org.chromium.webengine.shell.InstrumentationActivity;
 
 /**
- * Tests that basic fragment operations work as intended.
+ * Tests that fragment lifecycle works as expected.
  */
-@Batch(Batch.PER_CLASS)
 @RunWith(WebEngineJUnit4ClassRunner.class)
+@Batch(Batch.UNIT_TESTS)
 public class WebFragmentTest {
     @Rule
-    public EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
-
-    @Rule
     public InstrumentationActivityTestRule mActivityTestRule =
             new InstrumentationActivityTestRule();
 
-    private EmbeddedTestServer mServer;
     private WebSandbox mWebSandbox;
 
     @Before
     public void setUp() throws Throwable {
-        mServer = mTestServerRule.getServer();
-        mActivityTestRule.launchShell();
-        mWebSandbox = mActivityTestRule.getWebSandbox();
-    }
-
-    @After
-    public void tearDown() {
-        mActivityTestRule.finish();
-        runOnUiThreadBlocking(() -> mWebSandbox.shutdown());
-    }
-
-    private String getTestDataURL(String path) {
-        return mServer.getURL("/weblayer/test/data/" + path);
+        InstrumentationActivity activity = mActivityTestRule.launchShell();
+        mWebSandbox = activity.getWebSandboxFuture().get();
     }
 
     @Test
     @SmallTest
-    public void loadsPage() throws Exception {
-        WebFragment fragment = runOnUiThreadBlocking(() -> mWebSandbox.createFragment());
-        runOnUiThreadBlocking(() -> mActivityTestRule.attachFragment(fragment));
-
-        TabManager tabManager = fragment.getTabManager().get();
-        Tab activeTab = tabManager.getActiveTab().get();
-
-        Assert.assertEquals(activeTab.getDisplayUri(), Uri.EMPTY);
-
-        String url = getTestDataURL("simple_page.html");
-        mActivityTestRule.navigateAndWait(activeTab, url);
-
-        Assert.assertEquals(activeTab.getDisplayUri(), Uri.parse(url));
-    }
-
-    /**
-     * This test is similar to the previous one and just ensures that these unit tests can be
-     * batched.
-     */
-    @Test
-    @SmallTest
-    public void successfullyLoadDifferentPage() throws Exception {
-        mActivityTestRule.attachNewFragmentThenNavigateAndWait(getTestDataURL("simple_page2.html"));
-    }
-
-    @Test
-    @SmallTest
-    public void fragmentTabCanLoadMultiplePages() throws Exception {
-        mActivityTestRule.attachNewFragmentThenNavigateAndWait(getTestDataURL("simple_page.html"));
-
-        Tab tab = mActivityTestRule.getActiveTab();
-        mActivityTestRule.navigateAndWait(tab, getTestDataURL("simple_page2.html"));
-
-        Assert.assertTrue(tab.getDisplayUri().toString().endsWith("simple_page2.html"));
-    }
-
-    @Test
-    @SmallTest
-    public void fragmentsCanBeReplaced() throws Exception {
-        mActivityTestRule.attachNewFragmentThenNavigateAndWait(getTestDataURL("simple_page.html"));
-        // New fragment
-        mActivityTestRule.attachNewFragmentThenNavigateAndWait(getTestDataURL("simple_page2.html"));
-
-        Tab tab = mActivityTestRule.getActiveTab();
-        Assert.assertTrue(tab.getDisplayUri().toString().endsWith("simple_page2.html"));
-    }
-
-    @Test
-    @SmallTest
-    public void navigationFailure() {
-        try {
-            mActivityTestRule.attachNewFragmentThenNavigateAndWait(
-                    getTestDataURL("missingpage.html"));
-            Assert.fail("exception not thrown");
-        } catch (RuntimeException e) {
-            Assert.assertEquals(e.getMessage(), "Navigation failed.");
-        } catch (Exception e) {
-            Assert.fail("RuntimeException not thrown, instead got: " + e);
-        }
+    public void successfullyCreateFragment() {
+        Assert.assertNotNull(mWebSandbox);
+        Assert.assertNotNull(runOnUiThreadBlockingNoException(() -> mWebSandbox.createFragment()));
     }
 }
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AccessibilityTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AccessibilityTest.java
new file mode 100644
index 0000000..f7b914a6
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AccessibilityTest.java
@@ -0,0 +1,65 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that accessibility works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class AccessibilityTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    public void testSetTextScaling() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(
+                mActivityTestRule.getTestDataURL("shakespeare.html"));
+        int originalHeight = getTextHeight();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            getTestWebLayer().setTextScaling(activity.getBrowser().getProfile(), 2.0f);
+            return null;
+        });
+        assertThat(originalHeight).isLessThan(getTextHeight());
+    }
+
+    @Test
+    @SmallTest
+    public void testTextScalingSetsForceEnableZoom() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = activity.getBrowser().getProfile();
+            Assert.assertFalse(getTestWebLayer().getForceEnableZoom(profile));
+            getTestWebLayer().setTextScaling(profile, 2.0f);
+            Assert.assertTrue(getTestWebLayer().getForceEnableZoom(profile));
+            return null;
+        });
+    }
+
+    private int getTextHeight() {
+        return mActivityTestRule.executeScriptAndExtractInt(
+                "document.querySelector('p').clientHeight");
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(
+                mActivityTestRule.getActivity().getApplicationContext());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AutofillTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AutofillTest.java
new file mode 100644
index 0000000..a3ce9e7
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/AutofillTest.java
@@ -0,0 +1,131 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+import org.chromium.weblayer_private.test_interfaces.AutofillEventType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verifies Autofill works in WebLayer. The feature itself has AwAutofillTest.java for testing its
+ * functionality.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@MinAndroidSdkLevel(Build.VERSION_CODES.O)
+public class AutofillTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static final String MAIN_FRAME_FILE = "/main_frame.html";
+
+    private TestWebServer mWebServer;
+    private InstrumentationActivity mActivity;
+
+    private ArrayList<Integer> mEventsObserved;
+    private CallbackHelper mHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        mEventsObserved = new ArrayList<>();
+        mHelper = new CallbackHelper();
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        // There is no way to talk to TestWebLayer before the WebLayer is created.
+        // TestAutofillManagerWrapper can only replace AutofillProvider's AutofillMangerWrapper
+        // after initialization is done. So this test can't be used to test AutofillProvider's
+        // initialization. As WebLayer doesn't have specific code in AutofillProvider
+        // initialization, the AutofillProvider initialization is sufficiently tested via
+        // AwAutofillTest.
+        TestWebLayer.getTestWebLayer(mActivity.getApplicationContext())
+                .notifyOfAutofillEvents(
+                        mActivity.getBrowser(), () -> mHelper.notifyCalled(), mEventsObserved);
+        mWebServer = TestWebServer.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mWebServer.shutdown();
+    }
+
+    /**
+     * Verifies that Autofill is working for WebLayer as the TestAutofillManagerWrapper is
+     * receiving data from the renderer side.
+     */
+    @Test
+    @SmallTest
+    public void testBasicAutofill() throws Throwable {
+        final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+                + "<label>User Name:</label>"
+                + "<input type='text' id='text1' name='name' maxlength='30'"
+                + " placeholder='placeholder@placeholder.com' autocomplete='name given-name'>"
+                + "<input type='checkbox' id='checkbox1' name='showpassword'>"
+                + "<select id='select1' name='month'>"
+                + "<option value='1'>Jan</option>"
+                + "<option value='2'>Feb</option>"
+                + "</select><textarea id='textarea1'></textarea>"
+                + "<div contenteditable id='div1'>hello</div>"
+                + "<input type='submit'>"
+                + "<input type='reset' id='reset1'>"
+                + "<input type='color' id='color1'><input type='file' id='file1'>"
+                + "<input type='image' id='image1'>"
+                + "</form></body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, data, null);
+
+        // Load the test page.
+        mActivityTestRule.navigateAndWait(url);
+        int callCount = mHelper.getCallCount();
+        // Select the "text1" element.
+        mActivityTestRule.executeScriptSync("document.getElementById('text1').select();", false);
+        // Press "a" to trigger Autofill.
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
+
+        List<Integer> expected = new ArrayList(Arrays.asList(AutofillEventType.VIEW_ENTERED,
+                AutofillEventType.SESSION_STARTED, AutofillEventType.VALUE_CHANGED));
+        // We don't have the cancel event on P+, but we will see them on O and OMR1.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            expected.add(0, AutofillEventType.CANCEL);
+        }
+
+        // Wait for Autofill events.
+        mHelper.waitForCallback(
+                /* currentCallCount */ callCount, /* numberOfCallsToWaitFor */ expected.size(),
+                CallbackHelper.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        Assert.assertEquals(expected, mEventsObserved);
+    }
+
+    private void dispatchDownAndUpKeyEvents(final int code) throws Throwable {
+        long eventTime = SystemClock.uptimeMillis();
+        dispatchKeyEvent(new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, code, 0));
+        dispatchKeyEvent(new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, code, 0));
+    }
+
+    private boolean dispatchKeyEvent(final KeyEvent event) throws Throwable {
+        return TestThreadUtils.runOnUiThreadBlocking(() -> mActivity.dispatchKeyEvent(event));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BackgroundFetchTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BackgroundFetchTest.java
new file mode 100644
index 0000000..263049c
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BackgroundFetchTest.java
@@ -0,0 +1,145 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.filters.LargeTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.OpenUrlCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests Background Fetch and the OpenUrlCallback API.
+ */
+@MinWebLayerVersion(91)
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class BackgroundFetchTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestServer().getURL(
+                "/weblayer/test/data/background_fetch/index.html"));
+        getTestWebLayer().expediteDownloadService();
+    }
+
+    @Test
+    @LargeTest
+    @DisabledTest(message = "https://crbug.com/1272010")
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    public void basic() throws Exception {
+        Browser browser = mActivity.getBrowser();
+
+        int numTabs = runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(1, numTabs);
+
+        CallbackHelper tabAddedCallback = new CallbackHelper();
+        OpenUrlCallback openUrlCallback = new OpenUrlCallback() {
+            @Override
+            public Browser getBrowserForNewTab() {
+                return browser;
+            }
+
+            @Override
+            public void onTabAdded(Tab tab) {
+                tabAddedCallback.notifyCalled();
+            }
+        };
+
+        runOnUiThreadBlocking(
+                () -> { browser.getProfile().setTablessOpenUrlCallback(openUrlCallback); });
+
+        Assert.assertNull(getBackgroundFetchNotification(null));
+
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        // Wait for the notification to appear.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getBackgroundFetchNotification(null), Matchers.notNullValue());
+        }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL * 2, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+
+        // This part of the test passes locally but fails on some bots because the download never
+        // completes. TODO(estade): this also fails now that permissions are extra strict, so
+        // enabling this would require side-stepping permissions for this test. See
+        // crbug.com/1189247
+        //
+        // Wait for the notification to indicate completion (also testing the page receives the
+        // success message).
+        // CriteriaHelper.pollInstrumentationThread(() -> {
+        //    Criteria.checkThat(
+        //            getBackgroundFetchNotification("New Fetched Title!"),
+        //            Matchers.notNullValue());
+        // }, 30000, 100);
+
+        // -1 should be the ID of the first notification.
+        getTestWebLayer().activateBackgroundFetchNotification(-1);
+
+        // Tapping should add a new tab.
+        tabAddedCallback.waitForFirst();
+
+        numTabs = runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(2, numTabs);
+    }
+
+    /**
+     * Retrieves the first active background fetch notification it finds, or null if none exists.
+     *
+     *
+     * {@link NotificationManager#getActiveNotifications()} is only available from M.
+     *
+     * @param expectedTitle The title of the notification in question, or null if any notification
+     *         will do.
+     * @return The matched notification, or null.
+     */
+    @RequiresApi(Build.VERSION_CODES.M)
+    private Notification getBackgroundFetchNotification(String expectedTitle) {
+        StatusBarNotification notifications[] =
+                ((NotificationManager) mActivity.getApplicationContext().getSystemService(
+                         Context.NOTIFICATION_SERVICE))
+                        .getActiveNotifications();
+        for (StatusBarNotification statusBarNotification : notifications) {
+            if (statusBarNotification.getTag().equals("org.chromium.weblayer.downloads")) {
+                Notification notification = statusBarNotification.getNotification();
+                if (expectedTitle == null) return notification;
+
+                String title = notification.extras.getString(Notification.EXTRA_TITLE);
+                if (title != null && title.contains(expectedTitle)) return notification;
+            }
+        }
+        return null;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java
new file mode 100644
index 0000000..d7a8aa7
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java
@@ -0,0 +1,34 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import org.junit.Assert;
+
+import org.chromium.base.test.util.CallbackHelper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A CountDownLatch with a default timeout.
+ */
+public class BoundedCountDownLatch extends CountDownLatch {
+    BoundedCountDownLatch(int count) {
+        super(count);
+    }
+
+    /**
+     * This fails more quickly and gracefully than {@link CountDownLatch#await()}, which has no
+     * timeout. It gives useful error output, whereas a test that times out in {@link await()} may
+     * leave no stack.
+     */
+    public void timedAwait() {
+        try {
+            Assert.assertTrue(super.await(CallbackHelper.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            Assert.fail(e.toString());
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsHelper.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsHelper.java
new file mode 100644
index 0000000..428cec4
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsHelper.java
@@ -0,0 +1,99 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.RemoteException;
+import android.view.View;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Helper class for tests that interact with browser controls. Such tests should add the following
+ * above "public class FooTest":
+ * @CommandLineFlags.Add("enable-features=ImmediatelyHideBrowserControlsForTest")
+ */
+public final class BrowserControlsHelper {
+    private InstrumentationActivity mActivity;
+
+    // Height of the top-view. Set in waitForBrowserControlsInitialization().
+    private int mTopViewHeight;
+
+    // Blocks until browser controls are fully initialized. Should only be created in a test's
+    // setUp() method; see BrowserControlsHelper#createInSetUp().
+    private BrowserControlsHelper(InstrumentationActivity activity) throws Exception {
+        Assert.assertTrue(CommandLine.isInitialized());
+        Assert.assertTrue(CommandLine.getInstance().hasSwitch("enable-features"));
+        String enabledFeatures = CommandLine.getInstance().getSwitchValue("enable-features");
+        Assert.assertTrue(enabledFeatures.contains("ImmediatelyHideBrowserControlsForTest"));
+
+        mActivity = activity;
+
+        waitForBrowserControlsInitialization();
+    }
+
+    private Tab getActiveTab() {
+        return mActivity.getBrowser().getActiveTab();
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+    }
+
+    void waitForBrowserControlsViewToBeVisible(View v) {
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(v.getHeight(), Matchers.greaterThan(0));
+            Criteria.checkThat(v.getVisibility(), Matchers.is(View.VISIBLE));
+        });
+    }
+
+    // See TestWebLayer.waitForBrowserControlsMetadataState() for details on this.
+    void waitForBrowserControlsMetadataState(int top, int bottom) throws Exception {
+        CallbackHelper helper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                getTestWebLayer().waitForBrowserControlsMetadataState(
+                        getActiveTab(), top, bottom, () -> { helper.notifyCalled(); });
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        helper.waitForFirst();
+    }
+
+    // Ensures that browser controls are fully initialized and ready for scrolls to be processed.
+    private void waitForBrowserControlsInitialization() throws Exception {
+        // Poll until the top view becomes visible.
+        waitForBrowserControlsViewToBeVisible(mActivity.getTopContentsContainer());
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTopViewHeight = mActivity.getTopContentsContainer().getHeight();
+            Assert.assertTrue(mTopViewHeight > 0);
+        });
+
+        // Wait for cc to see the top-controls height.
+        waitForBrowserControlsMetadataState(mTopViewHeight, 0);
+    }
+
+    // Creates a BrowserControlsHelper instance and blocks until browser controls are fully
+    // initialized. Should be called from a test's setUp() method.
+    static BrowserControlsHelper createAndBlockUntilBrowserControlsInitializedInSetUp(
+            InstrumentationActivity activity) throws Exception {
+        return new BrowserControlsHelper(activity);
+    }
+
+    // Returns the height of the top view.
+    int getTopViewHeight() {
+        return mTopViewHeight;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsOffsetCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsOffsetCallbackTest.java
new file mode 100644
index 0000000..1f8a482
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsOffsetCallbackTest.java
@@ -0,0 +1,208 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.util.Range;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.BrowserControlsOffsetCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for ScrollOffsetCallback.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@CommandLineFlags.Add("enable-features=ImmediatelyHideBrowserControlsForTest")
+@DisabledTest(message = "https://crbug.com/1315399")
+public class BrowserControlsOffsetCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static final class BrowserControlsOffsetCallbackImpl
+            extends BrowserControlsOffsetCallback {
+        // All offsets are added to either |mTopOffsets| or |mBottomOffsets|.
+        public List<Integer> mTopOffsets = new ArrayList<>();
+        public List<Integer> mBottomOffsets = new ArrayList<>();
+        public CallbackHelper mCallbackHelper = new CallbackHelper();
+        // If non-null an offset and an offset lies in the specified range
+        // mCallbackHelper.notifyCalled() is invoked.
+        public Range<Integer> mTopTriggerRange;
+        public Range<Integer> mBottomTriggerRange;
+
+        @Override
+        public void onTopViewOffsetChanged(int offset) {
+            mTopOffsets.add(offset);
+            if (mTopTriggerRange != null && mTopTriggerRange.contains(offset)) {
+                mTopTriggerRange = null;
+                mCallbackHelper.notifyCalled();
+            }
+        }
+
+        @Override
+        public void onBottomViewOffsetChanged(int offset) {
+            mBottomOffsets.add(offset);
+            if (mBottomTriggerRange != null && mBottomTriggerRange.contains(offset)) {
+                mBottomTriggerRange = null;
+                mCallbackHelper.notifyCalled();
+            }
+        }
+    }
+
+    private BrowserControlsOffsetCallbackImpl mBrowserControlsOffsetCallback =
+            new BrowserControlsOffsetCallbackImpl();
+
+    private BrowserControlsHelper mBrowserControlsHelper;
+    private Point mCurrentDragPoint;
+    private boolean mInDrag;
+
+    @Before
+    public void setUp() throws Throwable {
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().registerBrowserControlsOffsetCallback(
+                    mBrowserControlsOffsetCallback);
+        });
+
+        mBrowserControlsHelper =
+                BrowserControlsHelper.createAndBlockUntilBrowserControlsInitializedInSetUp(
+                        activity);
+    }
+
+    private void startDrag() {
+        View view = mActivityTestRule.getActivity().getWindow().getDecorView();
+        Assert.assertFalse(mInDrag);
+        mInDrag = true;
+        view.post(() -> {
+            mCurrentDragPoint = new Point(view.getWidth() / 2, view.getHeight() / 2);
+            long eventTime = SystemClock.uptimeMillis();
+            view.dispatchTouchEvent(MotionEvent.obtain(eventTime, eventTime,
+                    MotionEvent.ACTION_DOWN, mCurrentDragPoint.x, mCurrentDragPoint.y, 0));
+        });
+    }
+
+    private void dragBy(final int deltaY) {
+        View view = mActivityTestRule.getActivity().getWindow().getDecorView();
+        Assert.assertTrue(mInDrag);
+        view.post(() -> {
+            long eventTime = SystemClock.uptimeMillis();
+            mCurrentDragPoint.y += deltaY;
+            view.dispatchTouchEvent(MotionEvent.obtain(eventTime, eventTime,
+                    MotionEvent.ACTION_MOVE, mCurrentDragPoint.x, mCurrentDragPoint.y, 0));
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testTopScroll() throws Exception {
+        int topViewHeight = mBrowserControlsHelper.getTopViewHeight();
+        CallbackHelper callbackHelper = mBrowserControlsOffsetCallback.mCallbackHelper;
+
+        // Scroll half the height.
+        mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(-topViewHeight + 2, -2);
+        startDrag();
+        dragBy(-topViewHeight / 2);
+        int callCount = 0;
+        callbackHelper.waitForCallback(callCount++);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBrowserControlsOffsetCallback.mTopOffsets.clear();
+            mBrowserControlsOffsetCallback.mTopTriggerRange =
+                    new Range(-topViewHeight, -topViewHeight);
+        });
+
+        // Scroll a lot to ensure top view completely hides.
+        dragBy(-topViewHeight);
+        callbackHelper.waitForCallback(callCount++);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBrowserControlsOffsetCallback.mTopOffsets.clear();
+            mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(-topViewHeight + 2, -2);
+        });
+
+        // Scroll up half the height to trigger showing again.
+        dragBy(topViewHeight / 2);
+        callbackHelper.waitForCallback(callCount++);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBrowserControlsOffsetCallback.mTopOffsets.clear();
+            mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(0, 0);
+        });
+
+        // And enough to be fully visible.
+        dragBy(topViewHeight);
+        callbackHelper.waitForCallback(callCount++);
+    }
+
+    @Test
+    @SmallTest
+    public void testBottomScroll() throws Exception {
+        CallbackHelper callbackHelper = mBrowserControlsOffsetCallback.mCallbackHelper;
+        int topViewHeight = mBrowserControlsHelper.getTopViewHeight();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View bottomView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TextView view = new TextView(activity);
+            view.setText("BOTTOM");
+            activity.getBrowser().setBottomView(view);
+            return view;
+        });
+
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+
+        int bottomViewHeight =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return bottomView.getHeight(); });
+        Assert.assertTrue(bottomViewHeight > 0);
+        // The amount necessary to scroll is the sum of the two views. This is because the page
+        // height is reduced by the sum of these two.
+        int maxViewsHeight = topViewHeight + bottomViewHeight;
+
+        // Wait for cc to see the bottom height. This is very important, as scrolling is gated by
+        // cc getting the bottom height.
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(topViewHeight, bottomViewHeight);
+
+        // Move by the size of the controls, which should hide both.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBrowserControlsOffsetCallback.mTopTriggerRange =
+                    new Range(-topViewHeight, -topViewHeight);
+            mBrowserControlsOffsetCallback.mBottomTriggerRange =
+                    new Range(bottomViewHeight, bottomViewHeight);
+        });
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -maxViewsHeight);
+        int callCount = 0;
+        // 2 is for the top and bottom.
+        callbackHelper.waitForCallback(callCount, 2);
+        callCount += 2;
+
+        // Move so top and bottom controls are shown again.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(0, 0);
+            mBrowserControlsOffsetCallback.mBottomTriggerRange = new Range(0, 0);
+        });
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, maxViewsHeight);
+        // 2 is for the top and bottom.
+        callbackHelper.waitForCallback(callCount, 2);
+        callCount += 2;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsTest.java
new file mode 100644
index 0000000..1301925c2
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserControlsTest.java
@@ -0,0 +1,636 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.app.Instrumentation;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+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.DisabledTest;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.content_public.browser.test.util.TestTouchUtils;
+import org.chromium.weblayer.BrowserControlsOffsetCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Test for bottom-controls.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@CommandLineFlags.Add("enable-features=ImmediatelyHideBrowserControlsForTest")
+public class BrowserControlsTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private BrowserControlsHelper mBrowserControlsHelper;
+
+    // Height of the top-view. Set in setUp().
+    private int mTopViewHeight;
+    // Height from the page (obtained using getVisiblePageHeight()) with the top-controls.
+    private int mPageHeightWithTopView;
+
+    /**
+     * Returns the visible height of the page as determined by JS. The returned value is in CSS
+     * pixels (which are most likely not the same as device pixels).
+     */
+    private int getVisiblePageHeight() {
+        return mActivityTestRule.executeScriptAndExtractInt("window.innerHeight");
+    }
+
+    private Tab getActiveTab() {
+        return mActivityTestRule.getActivity().getBrowser().getActiveTab();
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(
+                mActivityTestRule.getActivity().getApplicationContext());
+    }
+
+    private void setAccessibilityEnabled(boolean value) {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                getTestWebLayer().setAccessibilityEnabled(value);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private boolean canBrowserControlsScroll() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            try {
+                return getTestWebLayer().canBrowserControlsScroll(getActiveTab());
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private void createActivityWithTopView() throws Exception {
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+
+        mBrowserControlsHelper =
+                BrowserControlsHelper.createAndBlockUntilBrowserControlsInitializedInSetUp(
+                        activity);
+
+        mTopViewHeight = mBrowserControlsHelper.getTopViewHeight();
+        mPageHeightWithTopView = getVisiblePageHeight();
+    }
+
+    private View createBottomView() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View bottomView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TextView view = new TextView(activity);
+            view.setText("BOTTOM");
+            activity.getBrowser().setBottomView(view);
+            return view;
+        });
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+        int bottomViewHeight =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return bottomView.getHeight(); });
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(
+                mTopViewHeight, bottomViewHeight);
+        return bottomView;
+    }
+
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testTopAndBottom() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View bottomView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TextView view = new TextView(activity);
+            view.setText("BOTTOM");
+            activity.getBrowser().setBottomView(view);
+            return view;
+        });
+
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+
+        int bottomViewHeight =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return bottomView.getHeight(); });
+        Assert.assertTrue(bottomViewHeight > 0);
+        // The amount necessary to scroll is the sum of the two views. This is because the page
+        // height is reduced by the sum of these two.
+        int maxViewsHeight = mTopViewHeight + bottomViewHeight;
+
+        // Wait for cc to see the bottom height. This is very important, as scrolling is gated by
+        // cc getting the bottom height.
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(
+                mTopViewHeight, bottomViewHeight);
+
+        // Adding a bottom view should change the page height.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.not(mPageHeightWithTopView));
+        });
+        int pageHeightWithTopAndBottomViews = getVisiblePageHeight();
+        Assert.assertTrue(pageHeightWithTopAndBottomViews < mPageHeightWithTopView);
+
+        // Move by the size of the controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -maxViewsHeight);
+
+        // Moving should hide the bottom View.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(bottomView.getVisibility(), Matchers.is(View.INVISIBLE));
+        });
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    getVisiblePageHeight(), Matchers.greaterThan(mPageHeightWithTopView));
+        });
+
+        // Move so top and bottom-controls are shown again.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, maxViewsHeight);
+
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    getVisiblePageHeight(), Matchers.is(pageHeightWithTopAndBottomViews));
+        });
+    }
+
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testBottomOnly() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        // Remove the top-view.
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+
+        // Wait for cc to see the top-controls height change.
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(0, 0);
+
+        int pageHeightWithNoTopView = getVisiblePageHeight();
+        Assert.assertNotEquals(pageHeightWithNoTopView, mPageHeightWithTopView);
+
+        // Add in the bottom-view.
+        View bottomView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TextView view = new TextView(activity);
+            view.setText("BOTTOM");
+            activity.getBrowser().setBottomView(view);
+            return view;
+        });
+
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+        int bottomViewHeight =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return bottomView.getHeight(); });
+        Assert.assertTrue(bottomViewHeight > 0);
+        // Wait for cc to see the bottom-controls height change.
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(0, bottomViewHeight);
+
+        int pageHeightWithBottomView = getVisiblePageHeight();
+        Assert.assertNotEquals(pageHeightWithNoTopView, pageHeightWithBottomView);
+
+        // Move by the size of the bottom-controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -bottomViewHeight);
+
+        // Moving should hide the bottom-controls View.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(bottomView.getVisibility(), Matchers.is(View.INVISIBLE));
+        });
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(pageHeightWithNoTopView));
+        });
+
+        // Move so bottom-controls are shown again.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, bottomViewHeight);
+
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(pageHeightWithBottomView));
+        });
+    }
+
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testTopOnly() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View topView = activity.getTopContentsContainer();
+
+        // Move by the size of the top-controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+
+        // Moving should hide the top-controls and change the page height.
+        CriteriaHelper.pollUiThread(
+                () -> Criteria.checkThat(topView.getVisibility(), Matchers.is(View.INVISIBLE)));
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.not(mPageHeightWithTopView));
+        });
+
+        // Move so top-controls are shown again.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, mTopViewHeight);
+
+        // Wait for the page height to match initial height.
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(topView);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(mPageHeightWithTopView));
+        });
+    }
+
+    @MinWebLayerVersion(94)
+    @Test
+    @SmallTest
+    public void testEvents() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        View topView = activity.getTopContentsContainer();
+        View bottomView = createBottomView();
+
+        int fragmentHeight = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getFragment().getView().getHeight(); });
+        int pageHeight = getVisiblePageHeight();
+
+        // Record the maximum y position of clicks to detect if we clicked at the top or bottom.
+        mActivityTestRule.executeScriptSync(
+                "var max = 0; document.onclick = (e) => { max = Math.max(max, e.pageY); };",
+                true /* useSeparateIsolate */);
+
+        // Clicks on the bottom view should not propagate to the content layer.
+        TestTouchUtils.singleClickView(instrumentation, bottomView, 0, 0);
+        TestTouchUtils.sleepForDoubleTapTimeout(instrumentation);
+
+        // Click below the top view inside the content layer in the middle of the page.
+        TestTouchUtils.singleClickView(
+                instrumentation, topView, 0, topView.getHeight() + fragmentHeight / 2);
+        TestTouchUtils.sleepForDoubleTapTimeout(instrumentation);
+
+        // Wait until we see the click from the middle of the page.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            int max = mActivityTestRule.executeScriptAndExtractInt("max");
+            Criteria.checkThat(max, Matchers.greaterThan(pageHeight * 1 / 4));
+            Criteria.checkThat(max, Matchers.lessThan(pageHeight * 3 / 4));
+        });
+    }
+
+    @MinWebLayerVersion(100)
+    @Test
+    @SmallTest
+    public void testEventBelowView() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        View topContents = activity.getTopContentsContainer();
+
+        View fragmentView = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getFragment().getView(); });
+
+        // Move by the size of the top-controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+
+        // Moving should collapse the top-controls to their min height and change the page height.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(topContents.getVisibility(), Matchers.is(View.INVISIBLE));
+        });
+        // Click to stop any flings.
+        TestTouchUtils.singleClickView(instrumentation, activity.getWindow().getDecorView());
+        TestTouchUtils.sleepForDoubleTapTimeout(instrumentation);
+
+        // Record when page is clicked.
+        mActivityTestRule.executeScriptSync(
+                "var didClick = false; document.onclick = (e) => { didClick = true; };",
+                true /* useSeparateIsolate */);
+
+        // Click the area inside where top control would be if it's not hidden.
+        TestTouchUtils.singleClickView(instrumentation, fragmentView, 0, mTopViewHeight / 2);
+        TestTouchUtils.sleepForDoubleTapTimeout(instrumentation);
+
+        // Wait until page sees click.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            boolean didClick = mActivityTestRule.executeScriptAndExtractBoolean("didClick");
+            Criteria.checkThat(didClick, Matchers.is(true));
+        });
+    }
+
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testTopMinHeight() throws Exception {
+        createActivityWithTopView();
+        final int minHeight = 20;
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View topContents = activity.getTopContentsContainer();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.getBrowser().setTopView(topContents, minHeight, false, false));
+        int expectedCollapseAmount = topContents.getHeight() - minHeight;
+
+        // Make sure the top controls start out taller than the min height.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(topContents.getHeight(), Matchers.greaterThan(minHeight));
+        });
+
+        // Move by the size of the top-controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+
+        // Moving should collapse the top-controls to their min height and change the page height.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    getVisiblePageHeight(), Matchers.greaterThan(mPageHeightWithTopView));
+            Criteria.checkThat(
+                    topContents.getTranslationY(), Matchers.is((float) -expectedCollapseAmount));
+        });
+
+        // Move so top-controls are shown again.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, mTopViewHeight);
+
+        // Wait for the page height to match initial height.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(mPageHeightWithTopView));
+        });
+    }
+
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @DisabledTest(message = "https://crbug.com/1256861")
+    @Test
+    @SmallTest
+    public void testOnlyExpandTopControlsAtPageTop() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View topContents = activity.getTopContentsContainer();
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> activity.getBrowser().setTopView(
+                                topContents, 0, /*onlyExpandControlsAtPageTop=*/true, false));
+
+        // Scroll down past the top-controls, which should collapse the top-controls and change the
+        // page height.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -2 * mTopViewHeight);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    getVisiblePageHeight(), Matchers.greaterThan(mPageHeightWithTopView));
+            Criteria.checkThat(activity.getTopContentsContainer().getVisibility(),
+                    Matchers.is(View.INVISIBLE));
+        });
+
+        // Scroll part of the way up again, which should not show the top controls.
+        int scrolledPageHeight = getVisiblePageHeight();
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, mTopViewHeight);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(scrolledPageHeight));
+        });
+
+        // Scroll to the top to show the top controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, 2 * mTopViewHeight);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(mPageHeightWithTopView));
+            Criteria.checkThat(
+                    activity.getTopContentsContainer().getVisibility(), Matchers.is(View.VISIBLE));
+            Criteria.checkThat(topContents.getTranslationY(), Matchers.is(0.f));
+        });
+    }
+
+    // Makes sure that the top controls are not shown when a js dialog is shown when
+    // onlyExpandTopControlsAtPageTop is true and the page is scrolled down.
+    //
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @DisabledTest(message = "https://crbug.com/1319870")
+    @Test
+    @SmallTest
+    public void testAlertDoesntShowTopControlsIfOnlyExpandTopControlsAtPageTop() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        View topContents = activity.getTopContentsContainer();
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> activity.getBrowser().setTopView(
+                                topContents, 0, /*onlyExpandControlsAtPageTop=*/true, false));
+
+        // Scroll past the top-controls and wait until they're not visible.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -2 * mTopViewHeight);
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(activity.getTopContentsContainer().getVisibility(),
+                    Matchers.is(View.INVISIBLE));
+        });
+
+        // Trigger an alert dialog.
+        mActivityTestRule.executeScriptSync(
+                "window.setTimeout(function() { alert('alert dialog'); }, 1);", false);
+        onView(withText("alert dialog")).check(matches(isDisplayed()));
+
+        // Top controls shouldn't be shown.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(activity.getTopContentsContainer().getVisibility(),
+                    Matchers.is(View.INVISIBLE));
+        });
+    }
+
+    /**
+     * Makes sure that the top controls are shown when a js dialog is shown.
+     *
+     * Regression test for https://crbug.com/1078181.
+     *
+     * Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+     */
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testAlertShowsTopControls() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+
+        // Move by the size of the top-controls.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+
+        // Wait till top controls are invisible.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(activity.getTopContentsContainer().getVisibility(),
+                    Matchers.is(View.INVISIBLE));
+        });
+
+        // Trigger an alert dialog.
+        mActivityTestRule.executeScriptSync(
+                "window.setTimeout(function() { alert('alert'); }, 1);", false);
+
+        // Top controls are shown.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(
+                    activity.getTopContentsContainer().getVisibility(), Matchers.is(View.VISIBLE));
+        });
+    }
+
+    // Tests various assertions when accessibility is enabled.
+    // Disabled on L bots due to unexplained flakes. See crbug.com/1035894.
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testAccessibility() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+
+        // Scroll such that top-controls are hidden.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+        View topView = activity.getTopContentsContainer();
+        CriteriaHelper.pollUiThread(
+                () -> Criteria.checkThat(topView.getVisibility(), Matchers.is(View.INVISIBLE)));
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.not(mPageHeightWithTopView));
+        });
+
+        // Turn on accessibility, which should force the controls to show.
+        setAccessibilityEnabled(true);
+        mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(
+                activity.getTopContentsContainer());
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.is(mPageHeightWithTopView));
+        });
+
+        // When accessibility is enabled, the controls are not allowed to scroll.
+        Assert.assertFalse(canBrowserControlsScroll());
+
+        // Turn accessibility off, and verify the controls can scroll. This polls as
+        // setAccessibilityEnabled() is async.
+        setAccessibilityEnabled(false);
+        CriteriaHelper.pollInstrumentationThread(
+                () -> Criteria.checkThat(canBrowserControlsScroll(), Matchers.is(true)));
+    }
+
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testRemoveAllFromTopView() throws Exception {
+        createActivityWithTopView();
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+
+        // Install a different top-view.
+        FrameLayout newTopView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FrameLayout frameLayout = new FrameLayout(activity);
+            TextView textView = new TextView(activity);
+            textView.setText("new top");
+            frameLayout.addView(textView,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT,
+                            FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY));
+            activity.getBrowser().setTopView(frameLayout);
+            return frameLayout;
+        });
+
+        // Wait till new view is visible.
+        CriteriaHelper.pollUiThread(
+                () -> Criteria.checkThat(newTopView.getVisibility(), Matchers.is(View.VISIBLE)));
+        int newTopViewHeight =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return newTopView.getHeight(); });
+        Assert.assertNotEquals(newTopViewHeight, mTopViewHeight);
+        Assert.assertTrue(newTopViewHeight > 0);
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(newTopViewHeight, 0);
+
+        // Remove all, and ensure metadata and page-height change.
+        TestThreadUtils.runOnUiThreadBlocking(() -> { newTopView.removeAllViews(); });
+        mBrowserControlsHelper.waitForBrowserControlsMetadataState(0, 0);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.not(mPageHeightWithTopView));
+        });
+    }
+
+    private void registerBrowserControlsOffsetCallbackForOffset(
+            CallbackHelper helper, int targetOffset) {
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().registerBrowserControlsOffsetCallback(
+                    new BrowserControlsOffsetCallback() {
+                        @Override
+                        public void onTopViewOffsetChanged(int offset) {
+                            if (offset == targetOffset) {
+                                activity.getBrowser().unregisterBrowserControlsOffsetCallback(this);
+                                helper.notifyCalled();
+                            }
+                        }
+                    });
+        });
+    }
+
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testTopExpandedWhenOnlyExpandAtTop() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_ONLY_EXPAND_CONTROLS_AT_TOP, true);
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShell(extras);
+        CallbackHelper helper = new CallbackHelper();
+        registerBrowserControlsOffsetCallbackForOffset(helper, 0);
+        mActivityTestRule.navigateAndWait(url);
+        helper.waitForFirst();
+    }
+
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @Test
+    @SmallTest
+    public void testTopExpandedOnLoadWhenOnlyExpandAtTop() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_ONLY_EXPAND_CONTROLS_AT_TOP, true);
+        InstrumentationActivity activity = mActivityTestRule.launchShell(extras);
+        CallbackHelper helper = new CallbackHelper();
+        registerBrowserControlsOffsetCallbackForOffset(helper, 0);
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("tall_page.html"));
+        int callCount = 0;
+        helper.waitForCallback(callCount++);
+
+        mTopViewHeight = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return activity.getTopContentsContainer().getHeight(); });
+        Assert.assertNotEquals(mTopViewHeight, 0);
+
+        // Scroll such that top-controls are hidden.
+        registerBrowserControlsOffsetCallbackForOffset(helper, -mTopViewHeight);
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -mTopViewHeight);
+        helper.waitForCallback(callCount++);
+
+        // Load a new page. The top-controls should be shown again.
+        registerBrowserControlsOffsetCallbackForOffset(helper, 0);
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("simple_page.html"));
+        helper.waitForCallback(callCount++);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
new file mode 100644
index 0000000..645641e
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
@@ -0,0 +1,733 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.BrowserRestoreCallback;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
+import org.chromium.weblayer.NavigationController;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.WebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests that fragment lifecycle works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class BrowserFragmentLifecycleTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private Tab getTab() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mActivityTestRule.getActivity().getTab());
+    }
+
+    private Browser getBrowser() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mActivityTestRule.getActivity().getBrowser());
+    }
+
+    private Fragment getFragment() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mActivityTestRule.getActivity().getFragment());
+    }
+
+    private boolean isRestoringPreviousState() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mActivityTestRule.getActivity().getBrowser().isRestoringPreviousState());
+    }
+
+    private int getSupportedMajorVersion() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> WebLayer.getSupportedMajorVersion(mActivityTestRule.getActivity()));
+    }
+
+    @Test
+    @SmallTest
+    public void successfullyLoadsUrlAfterRecreation() {
+        mActivityTestRule.launchShellWithUrl("about:blank");
+        String url = "data:text,foo";
+        mActivityTestRule.navigateAndWait(getTab(), url, false);
+
+        mActivityTestRule.recreateActivity();
+
+        url = "data:text,bar";
+        mActivityTestRule.navigateAndWait(getTab(), url, false);
+    }
+
+    @Test
+    @SmallTest
+    public void restoreAfterRecreate() throws Throwable {
+        mActivityTestRule.launchShellWithUrl("about:blank");
+        String url = "data:text,foo";
+        mActivityTestRule.navigateAndWait(getTab(), url, false);
+
+        mActivityTestRule.recreateActivity();
+
+        waitForTabToFinishRestore(getTab(), url);
+    }
+
+    /**
+     * Helper class used to ensure that during recreation the following are called:
+     * . TabListCallback#onTabAdded().
+     * . NavigationCallback#onNavigationStarted().
+     */
+    private static final class RecreateCallbackTracker {
+        private final ActivityLifecycleCallback mStateListener;
+        private boolean mGotCreate;
+        private boolean mGotOnTabAdded;
+        private boolean mGotOnNavigationStarted;
+
+        RecreateCallbackTracker(Browser browser) {
+            mStateListener = (Activity newActivity, Stage newStage) -> {
+                if (newStage == Stage.CREATED && !mGotCreate) {
+                    mGotCreate = true;
+                    Browser newBrowser = ((InstrumentationActivity) newActivity).getBrowser();
+                    Assert.assertTrue(newBrowser.getTabs().isEmpty());
+                    Assert.assertNotEquals(newBrowser, browser);
+                    newBrowser.registerTabListCallback(new TabListCallback() {
+                        @Override
+                        public void onTabAdded(@NonNull Tab tab) {
+                            mGotOnTabAdded = true;
+                            tab.getNavigationController().registerNavigationCallback(
+                                    new NavigationCallback() {
+                                        @Override
+                                        public void onNavigationStarted(
+                                                @NonNull Navigation navigation) {
+                                            mGotOnNavigationStarted = true;
+                                        }
+                                    });
+                        }
+                    });
+                    // This is needed to ensure the callback added above runs first. Without it the
+                    // tab is made active onTabAdded(), resulting in onNavigationStarted() never
+                    // being called (because the navigation starts from setActiveTab(), before the
+                    // callback is added).
+                    ((InstrumentationActivity) newActivity).reregisterTabListCallback();
+                }
+            };
+            ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mStateListener);
+        }
+
+        public void runAssertsAfterRecreate() {
+            Assert.assertTrue(mGotCreate);
+            Assert.assertTrue(mGotOnTabAdded);
+            Assert.assertTrue(mGotOnNavigationStarted);
+            ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mStateListener);
+        }
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(99)
+    public void restoreAfterRecreateCallsTabAndNavigationCallbacks() throws Throwable {
+        mActivityTestRule.launchShellWithUrl("about:blank");
+        String url = "data:text,foo";
+        mActivityTestRule.navigateAndWait(getTab(), url, false);
+        final RecreateCallbackTracker tracker = new RecreateCallbackTracker(getBrowser());
+
+        mActivityTestRule.recreateActivity();
+
+        waitForTabToFinishRestore(getTab(), url);
+        tracker.runAssertsAfterRecreate();
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(98)
+    public void setMaxNavigationsPerTabForInstanceState() throws Throwable {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Browser.setMaxNavigationsPerTabForInstanceState(4));
+
+        // Navigate to 5 urls.
+        mActivityTestRule.launchShellWithUrl("about:blank");
+        final String url1 = "data:text,foo";
+        mActivityTestRule.navigateAndWait(getTab(), url1, false);
+        final String url2 = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.navigateAndWait(getTab(), url2, false);
+        final String url3 = mActivityTestRule.getTestDataURL("simple_page2.html");
+        mActivityTestRule.navigateAndWait(getTab(), url3, false);
+        final String url4 = mActivityTestRule.getTestDataURL("simple_page3.html");
+        mActivityTestRule.navigateAndWait(getTab(), url4, false);
+        final String url5 = mActivityTestRule.getTestDataURL("simple_page4.html");
+        mActivityTestRule.navigateAndWait(getTab(), url5, false);
+
+        mActivityTestRule.recreateActivity();
+
+        // The max set to 4, so only 4 navigation entries should be persisted.
+        waitForTabToFinishRestore(getTab(), url5);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            final NavigationController navigationController =
+                    mActivityTestRule.getActivity().getTab().getNavigationController();
+            Assert.assertEquals(4, navigationController.getNavigationListSize());
+            Assert.assertEquals(
+                    Uri.parse(url5), navigationController.getNavigationEntryDisplayUri(3));
+        });
+    }
+
+    private void destroyFragment(CallbackHelper helper) {
+        FragmentManager fm = mActivityTestRule.getActivity().getSupportFragmentManager();
+        fm.beginTransaction()
+                .remove(fm.getFragments().get(0))
+                .runOnCommit(helper::notifyCalled)
+                .commit();
+    }
+
+    // https://crbug.com/1021041
+    @Test
+    @SmallTest
+    public void handlesFragmentDestroyWhileNavigating() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        CallbackHelper helper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationStarted(@NonNull Navigation navigation) {
+                    destroyFragment(helper);
+                }
+            });
+            navigationController.navigate(Uri.parse("data:text,foo"));
+        });
+        helper.waitForFirst();
+    }
+
+    // Waits for |tab| to finish loadding |url. This is intended to be called after restore.
+    private void waitForTabToFinishRestore(Tab tab, String url) {
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // It's possible the NavigationController hasn't loaded yet, handle either scenario.
+            NavigationController navigationController = tab.getNavigationController();
+            for (int i = 0; i < navigationController.getNavigationListSize(); ++i) {
+                if (navigationController.getNavigationEntryDisplayUri(i).equals(Uri.parse(url))) {
+                    latch.countDown();
+                    return;
+                }
+            }
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationCompleted(@NonNull Navigation navigation) {
+                    if (navigation.getUri().equals(Uri.parse(url))) {
+                        latch.countDown();
+                    }
+                }
+            });
+        });
+        latch.timedAwait();
+    }
+
+    // Recreates the activity and waits for the first tab to be restored. |extras| is the Bundle
+    // used to launch the shell.
+    private void restoresPreviousSession(Bundle extras) {
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, "x");
+        final String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+        if (getSupportedMajorVersion() >= 88) {
+            Assert.assertFalse(isRestoringPreviousState());
+        }
+
+        mActivityTestRule.recreateActivity();
+
+        Tab tab = getTab();
+        Assert.assertNotNull(tab);
+        waitForTabToFinishRestore(tab, url);
+        if (getSupportedMajorVersion() >= 88) {
+            Assert.assertFalse(isRestoringPreviousState());
+        }
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(99)
+    public void restoresPreviousSessionAndNotifiesCallbacks() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, "x");
+        final String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+        final RecreateCallbackTracker tracker = new RecreateCallbackTracker(getBrowser());
+
+        mActivityTestRule.recreateActivity();
+
+        Tab tab = getTab();
+        Assert.assertNotNull(tab);
+        waitForTabToFinishRestore(tab, url);
+        tracker.runAssertsAfterRecreate();
+    }
+
+    @Test
+    @SmallTest
+    public void restoresPreviousSession() throws Throwable {
+        restoresPreviousSession(new Bundle());
+    }
+
+    @Test
+    @SmallTest
+    public void restoresPreviousSessionIncognito() throws Throwable {
+        Bundle extras = new Bundle();
+        // This forces incognito.
+        extras.putString(InstrumentationActivity.EXTRA_PROFILE_NAME, null);
+        restoresPreviousSession(extras);
+    }
+
+    @Test
+    @SmallTest
+    public void restoresTabGuid() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, "x");
+        final String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+        final String initialTabId = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getTab().getGuid(); });
+        Assert.assertNotNull(initialTabId);
+        Assert.assertFalse(initialTabId.isEmpty());
+
+        mActivityTestRule.recreateActivity();
+
+        Tab tab = getTab();
+        Assert.assertNotNull(tab);
+        waitForTabToFinishRestore(tab, url);
+        final String restoredTabId =
+                TestThreadUtils.runOnUiThreadBlockingNoException(() -> { return tab.getGuid(); });
+        Assert.assertEquals(initialTabId, restoredTabId);
+    }
+
+    @Test
+    @SmallTest
+    public void restoreTabGuidAfterRecreate() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        final Tab tab = getTab();
+        final String initialTabId =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return tab.getGuid(); });
+        String url = "data:text,foo";
+        mActivityTestRule.navigateAndWait(tab, url, false);
+
+        mActivityTestRule.recreateActivity();
+
+        final Tab restoredTab = getTab();
+        Assert.assertNotEquals(tab, restoredTab);
+        waitForTabToFinishRestore(restoredTab, url);
+        final String restoredTabId = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return restoredTab.getGuid(); });
+        Assert.assertEquals(initialTabId, restoredTabId);
+    }
+
+    @Test
+    @SmallTest
+    public void useViewModelDoesntRecreateBrowser() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_USE_VIEW_MODEL, true);
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        final Browser browser = getBrowser();
+        final Tab tab = getTab();
+        final Fragment fragment = getFragment();
+
+        mActivityTestRule.recreateActivity();
+
+        // The tab and browser should not have changed.
+        Assert.assertEquals(tab, getTab());
+        Assert.assertEquals(browser, getBrowser());
+        Assert.assertFalse(
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.isDestroyed(); }));
+        // But the fragment should have.
+        Assert.assertNotEquals(fragment, getFragment());
+    }
+
+    @Test
+    @SmallTest
+    public void useViewModelDestroysBrowserWhenActivityDestroyed() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_USE_VIEW_MODEL, true);
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        final Browser browser = getBrowser();
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onWillDestroyBrowserAndAllTabs() {
+                    callbackHelper.notifyCalled();
+                }
+            });
+            activity.finish();
+        });
+        callbackHelper.waitForFirst();
+        Assert.assertTrue(
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.isDestroyed(); }));
+    }
+
+    @Test
+    @SmallTest
+    public void useViewModelDestroysBrowserWhenFragmentDestroyed() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_USE_VIEW_MODEL, true);
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        final Browser browser = getBrowser();
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onWillDestroyBrowserAndAllTabs() {
+                    callbackHelper.notifyCalled();
+                }
+            });
+            destroyFragment(callbackHelper);
+        });
+        // There are two callbacks, one from destroying the fragment, and the second from
+        // onWillDestroyBrowserAndAllTabs().
+        callbackHelper.waitForCallback(0, 2);
+        Assert.assertTrue(
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.isDestroyed(); }));
+    }
+
+    @Test
+    @SmallTest
+    public void restoresTabData() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, "x");
+
+        Map<String, String> initialData = new HashMap<>();
+        initialData.put("foo", "bar");
+        restoreTabData(extras, initialData);
+    }
+
+    @Test
+    @SmallTest
+    public void restoreTabDataAfterRecreate() throws Throwable {
+        Map<String, String> initialData = new HashMap<>();
+        initialData.put("foo", "bar");
+        restoreTabData(new Bundle(), initialData);
+    }
+
+    private void restoreTabData(Bundle extras, Map<String, String> initialData) {
+        String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = mActivityTestRule.getActivity().getTab();
+            Assert.assertTrue(tab.getData().isEmpty());
+            tab.setData(initialData);
+        });
+
+        mActivityTestRule.recreateActivity();
+
+        Tab tab = getTab();
+        Assert.assertNotNull(tab);
+        waitForTabToFinishRestore(tab, url);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertEquals(initialData, tab.getData()));
+    }
+
+    @Test
+    @SmallTest
+    public void getAndRemoveBrowserPersistenceIds() throws Throwable {
+        // Creates a browser with the persistence id 'x'.
+        final String persistenceId = "x";
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId);
+        final String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+
+        // Destroy the frament, which ensures the persistence file was written to.
+        CallbackHelper helper = new CallbackHelper();
+        Profile profile = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser().getProfile(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> destroyFragment(helper));
+        helper.waitForCallback(0, 1);
+        int callCount = helper.getCallCount();
+
+        // Verify the id can be fetched.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            profile.getBrowserPersistenceIds((Set<String> ids) -> {
+                Assert.assertEquals(1, ids.size());
+                Assert.assertTrue(ids.contains(persistenceId));
+                helper.notifyCalled();
+            });
+        });
+        helper.waitForCallback(callCount, 1);
+        callCount = helper.getCallCount();
+
+        // Remove the storage.
+        HashSet<String> ids = new HashSet<String>();
+        ids.add("x");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            profile.removeBrowserPersistenceStorage(ids, (Boolean result) -> {
+                Assert.assertTrue(result);
+                helper.notifyCalled();
+            });
+        });
+        helper.waitForCallback(callCount, 1);
+        callCount = helper.getCallCount();
+
+        // Verify it was actually removed.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            profile.getBrowserPersistenceIds((Set<String> actualIds) -> {
+                Assert.assertTrue(actualIds.isEmpty());
+                helper.notifyCalled();
+            });
+        });
+        helper.waitForCallback(callCount, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void browserAndTabIsDestroyedWhenFragmentDestroyed() throws Throwable {
+        mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html"));
+
+        CallbackHelper helper = new CallbackHelper();
+        Browser browser = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser(); });
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getActiveTab(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse(browser.isDestroyed());
+            Assert.assertFalse(tab.isDestroyed());
+            destroyFragment(helper);
+        });
+        helper.waitForFirst();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(browser.isDestroyed());
+            Assert.assertTrue(tab.isDestroyed());
+        });
+    }
+
+    // Used to track new Browsers finish restoring.
+    private static final class BrowserRestoreHelper
+            implements InstrumentationActivity.OnCreatedCallback {
+        private final CallbackHelper mCallbackHelper;
+        public List<Browser> mBrowsers;
+
+        // |helper| is notified once restore is complete (or if a browser is created already
+        // restored).
+        BrowserRestoreHelper(CallbackHelper helper) {
+            mCallbackHelper = helper;
+            mBrowsers = new ArrayList<Browser>();
+            InstrumentationActivity.registerOnCreatedCallback(this);
+        }
+
+        @Override
+        public void onCreated(Browser browser, InstrumentationActivity activity) {
+            mBrowsers.add(browser);
+            if (!browser.isRestoringPreviousState()) {
+                mCallbackHelper.notifyCalled();
+                return;
+            }
+            browser.registerBrowserRestoreCallback(new BrowserRestoreCallback() {
+                @Override
+                public void onRestoreCompleted() {
+                    Assert.assertFalse(browser.isRestoringPreviousState());
+                    browser.unregisterBrowserRestoreCallback(this);
+                    mCallbackHelper.notifyCalled();
+                }
+            });
+        }
+    }
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    public void restoreUsingOnRestoreCompleted() throws Throwable {
+        final String persistenceId = "x";
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        BrowserRestoreHelper restoreHelper = new BrowserRestoreHelper(callbackHelper);
+
+        final String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url, extras);
+        // Wait for the restore to complete.
+        callbackHelper.waitForCallback(0, 1);
+
+        // Recreate and wait for restore.
+        mActivityTestRule.recreateActivity();
+        callbackHelper.waitForCallback(1, 1);
+    }
+
+    private String getCurrentDisplayUri(Browser browser) {
+        NavigationController navigationController =
+                browser.getActiveTab().getNavigationController();
+        return navigationController
+                .getNavigationEntryDisplayUri(navigationController.getNavigationListCurrentIndex())
+                .toString();
+    }
+
+    @Test
+    @SmallTest
+    public void twoFragmentsDifferentIncognitoProfiles() throws Throwable {
+        // This test creates two browsers with different profile names and persistence ids.
+        final String persistenceId1 = "x";
+        final String persistenceId2 = "y";
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId1);
+        extras.putString(InstrumentationActivity.EXTRA_PROFILE_NAME, persistenceId1);
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        BrowserRestoreHelper restoreHelper = new BrowserRestoreHelper(callbackHelper);
+        final String url1 = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url1, extras);
+
+        // Wait for the restore to complete.
+        int currentCallCount = 0;
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        // Create another fragment
+        Browser newBrowser = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            InstrumentationActivity activity = mActivityTestRule.getActivity();
+            Intent intent = new Intent(activity.getIntent());
+            intent.putExtra(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId2);
+            intent.putExtra(InstrumentationActivity.EXTRA_PROFILE_NAME, persistenceId2);
+
+            // The newly created browser should have a different Profile, but be incognito.
+            Browser browser = Browser.fromFragment(
+                    activity.createBrowserFragment(android.R.id.content, intent));
+            Assert.assertNotEquals(browser, activity.getBrowser());
+            Assert.assertNotEquals(browser.getProfile(), activity.getBrowser().getProfile());
+            Assert.assertTrue(activity.getBrowser().getProfile().isIncognito());
+            Assert.assertTrue(browser.getProfile().isIncognito());
+            return browser;
+        });
+
+        // Wait for restore.
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        // Navigate to url2.
+        final String url2 = mActivityTestRule.getTestDataURL("simple_page2.html");
+        Tab newTab = TestThreadUtils.runOnUiThreadBlocking(() -> newBrowser.getActiveTab());
+        mActivityTestRule.navigateAndWait(newTab, url2, true);
+
+        Profile[] profiles = new Profile[2];
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            profiles[0] = mActivityTestRule.getActivity().getBrowser().getProfile();
+            profiles[1] = newBrowser.getProfile();
+        });
+
+        // Recreate the activity and wait for two restores (for the two fragments).
+        InstrumentationActivity.sAllowMultipleFragments = true;
+        restoreHelper.mBrowsers.clear();
+        mActivityTestRule.recreateActivity();
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Two new Browsers should be created, but have the profiles created earlier.
+            Assert.assertEquals(2, restoreHelper.mBrowsers.size());
+            Browser restoredBrowser1 = restoreHelper.mBrowsers.get(0);
+            Browser restoredBrowser2 = restoreHelper.mBrowsers.get(1);
+            if (restoredBrowser2.getProfile().getName().equals(persistenceId1)) {
+                restoredBrowser1 = restoreHelper.mBrowsers.get(1);
+                restoredBrowser2 = restoreHelper.mBrowsers.get(0);
+            }
+            Assert.assertEquals(restoredBrowser1.getProfile().getName(), persistenceId1);
+            Assert.assertTrue(restoredBrowser1.getProfile().isIncognito());
+            Assert.assertEquals(profiles[0], restoredBrowser1.getProfile());
+            Assert.assertEquals(url1, getCurrentDisplayUri(restoredBrowser1));
+
+            Assert.assertEquals(restoredBrowser2.getProfile().getName(), persistenceId2);
+            Assert.assertTrue(restoredBrowser2.getProfile().isIncognito());
+            Assert.assertEquals(profiles[1], restoredBrowser2.getProfile());
+            Assert.assertEquals(url2, getCurrentDisplayUri(restoredBrowser2));
+            Assert.assertNotEquals(restoredBrowser2, newBrowser);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void twoFragmentsSameIncognitoProfile() throws Throwable {
+        // This test creates two browsers with the same profile, but different persistence ids.
+        final String persistenceId1 = "x";
+        final String persistenceId2 = "y";
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId1);
+        extras.putString(InstrumentationActivity.EXTRA_PROFILE_NAME, persistenceId1);
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        BrowserRestoreHelper restoreHelper = new BrowserRestoreHelper(callbackHelper);
+
+        final String url1 = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.launchShellWithUrl(url1, extras);
+        // Wait for the restore to complete.
+        int currentCallCount = 0;
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        // Create another fragment
+        Browser newBrowser = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            InstrumentationActivity activity = mActivityTestRule.getActivity();
+            Intent intent = new Intent(activity.getIntent());
+            intent.putExtra(InstrumentationActivity.EXTRA_PERSISTENCE_ID, persistenceId2);
+
+            // The newly created browser should have a different Profile, but be incognito.
+            Browser browser = Browser.fromFragment(
+                    activity.createBrowserFragment(android.R.id.content, intent));
+            Assert.assertNotEquals(browser, activity.getBrowser());
+            Assert.assertEquals(browser.getProfile(), activity.getBrowser().getProfile());
+            Assert.assertTrue(activity.getBrowser().getProfile().isIncognito());
+            return browser;
+        });
+        Profile profile = TestThreadUtils.runOnUiThreadBlocking(() -> newBrowser.getProfile());
+        // Wait for restore.
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        // Navigate to url2.
+        final String url2 = mActivityTestRule.getTestDataURL("simple_page2.html");
+        Tab newTab = TestThreadUtils.runOnUiThreadBlocking(() -> newBrowser.getActiveTab());
+        mActivityTestRule.navigateAndWait(newTab, url2, true);
+
+        // Recreate the activity and wait for two restores (for the two fragments).
+        InstrumentationActivity.sAllowMultipleFragments = true;
+        restoreHelper.mBrowsers.clear();
+        mActivityTestRule.recreateActivity();
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+        callbackHelper.waitForCallback(currentCallCount++, 1);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Two new Browsers should be created.
+            Assert.assertEquals(2, restoreHelper.mBrowsers.size());
+            Browser restoredBrowser1 = restoreHelper.mBrowsers.get(0);
+            Browser restoredBrowser2 = restoreHelper.mBrowsers.get(1);
+            Assert.assertEquals(profile, restoredBrowser1.getProfile());
+
+            Assert.assertEquals(profile, restoredBrowser2.getProfile());
+            Assert.assertNotEquals(restoredBrowser2, newBrowser);
+
+            if (getCurrentDisplayUri(restoredBrowser1).equals(url1)) {
+                Assert.assertEquals(url2, getCurrentDisplayUri(restoredBrowser2));
+            } else {
+                Assert.assertEquals(url1, getCurrentDisplayUri(restoredBrowser2));
+                Assert.assertEquals(url2, getCurrentDisplayUri(restoredBrowser1));
+            }
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java
new file mode 100644
index 0000000..99eb13a02
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserTest.java
@@ -0,0 +1,88 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests for Browser.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class BrowserTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    public void testDestroyTab() {
+        String url = mActivityTestRule.getTestDataURL("before_unload.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            Tab tab = browser.getActiveTab();
+            Assert.assertFalse(tab.isDestroyed());
+            browser.destroyTab(tab);
+            Assert.assertTrue(tab.isDestroyed());
+        });
+    }
+
+    private boolean isPageVisible() {
+        return mActivityTestRule.executeScriptAndExtractBoolean(
+                "document.visibilityState === 'visible'");
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(91)
+    public void testSetChangeVisibilityOnNextDetach() {
+        String url = mActivityTestRule.getTestDataURL("visibility.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        // Force 'gotHide' to initially be false.
+        mActivityTestRule.executeScriptSync("gotHide = false;", false);
+
+        // Force the page to be visible during detach, detach the Fragment, and ensure the page is
+        // still visible.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().setChangeVisibilityOnNextDetach(false);
+            FragmentManager fm = mActivity.getSupportFragmentManager();
+            fm.beginTransaction().detach(mActivityTestRule.getFragment()).commitNow();
+        });
+        Assert.assertFalse(mActivityTestRule.executeScriptAndExtractBoolean("gotHide", false));
+        Assert.assertTrue(isPageVisible());
+
+        // Attach the Fragment, the page should still be visible.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FragmentManager fm = mActivity.getSupportFragmentManager();
+            fm.beginTransaction().attach(mActivityTestRule.getFragment()).commitNow();
+        });
+        Assert.assertFalse(mActivityTestRule.executeScriptAndExtractBoolean("gotHide", false));
+        Assert.assertTrue(isPageVisible());
+
+        // Detach the Fragment. Because setChangeVisibilityOnNextDetach() was reset as part of
+        // attach, the page should no longer be visible and 'gotHide' should be true.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FragmentManager fm = mActivity.getSupportFragmentManager();
+            fm.beginTransaction().detach(mActivityTestRule.getFragment()).commitNow();
+        });
+        Assert.assertTrue(mActivityTestRule.executeScriptAndExtractBoolean("gotHide", false));
+        Assert.assertFalse(isPageVisible());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BundleLanguageTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BundleLanguageTest.java
new file mode 100644
index 0000000..e1101e64
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BundleLanguageTest.java
@@ -0,0 +1,137 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Locale;
+
+/** Tests that translations work correctly for Java strings inside bundles. */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@DisabledTest(message = "crbug.com/1226712")
+public class BundleLanguageTest {
+    private static final String WEBLAYER_SPECIFIC_STRING =
+            "string/infobar_missing_location_permission_text";
+    private static final String SHARED_STRING = "string/color_picker_dialog_title";
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private Context mRemoteContext;
+    private Context mWebLayerContext;
+
+    @Before
+    public void setUp() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        mRemoteContext = TestWebLayer.getRemoteContext(activity.getApplicationContext());
+        mWebLayerContext = TestWebLayer.getWebLayerContext(activity.getApplicationContext());
+    }
+
+    @Test
+    @SmallTest
+    public void testWebLayerString() throws Exception {
+        // The bundle tests have both "es" and "fr" splits installed, so each of these should have a
+        // separate translation.
+        HashSet<String> translations = new HashSet<>();
+        for (String locale : new String[] {"en", "es", "fr"}) {
+            translations.add(getStringForLocale(WEBLAYER_SPECIFIC_STRING, locale));
+        }
+        Assert.assertEquals(3, translations.size());
+
+        // The "ko" language split is not installed, so should fall back to english.
+        Assert.assertEquals(getStringForLocale(WEBLAYER_SPECIFIC_STRING, "en"),
+                getStringForLocale(WEBLAYER_SPECIFIC_STRING, "ko"));
+    }
+
+    @Test
+    @SmallTest
+    public void testSharedString() throws Exception {
+        // This string is shared with WebView, so should have a separate translation for all
+        // locales, even locales without splits installed.
+        HashSet<String> translations = new HashSet<>();
+        for (String locale : new String[] {"en", "es", "fr", "ko"}) {
+            translations.add(getStringForLocale(SHARED_STRING, locale));
+        }
+        Assert.assertEquals(4, translations.size());
+    }
+
+    /**
+     * Tests that all locale resources have been moved into splits, so the only package ID left in
+     * the base APK has a dynamic ID.
+     */
+    @Test
+    @SmallTest
+    public void testBasePackageIdCorrect() throws Exception {
+        AssetManager assetManager = createEmptyAssetManager();
+        addAssetPath(assetManager, mWebLayerContext.getApplicationInfo().sourceDir);
+        SparseArray<String> packageIds = getPackageIds(assetManager);
+        Assert.assertEquals(2, packageIds.size());
+        Assert.assertEquals(packageIds.get(1), "android");
+        Assert.assertEquals(packageIds.get(2), mWebLayerContext.getPackageName());
+    }
+
+    /** Tests that locale splits only have resources from the hardcoded locale package ID. */
+    @Test
+    @SmallTest
+    public void testLocalePackageIdCorrect() throws Exception {
+        AssetManager assetManager = createEmptyAssetManager();
+        for (String path : mWebLayerContext.getApplicationInfo().splitSourceDirs) {
+            addAssetPath(assetManager, path);
+        }
+        SparseArray<String> packageIds = getPackageIds(assetManager);
+        Assert.assertEquals(2, packageIds.size());
+        Assert.assertEquals(packageIds.get(1), "android");
+        Assert.assertEquals(packageIds.get(ResourceUtil.REQUIRED_PACKAGE_IDENTIFIER),
+                mWebLayerContext.getPackageName() + "_translations");
+    }
+
+    private String getStringForLocale(String name, String locale) {
+        Resources resources = mRemoteContext.getResources();
+        Configuration config = resources.getConfiguration();
+        config.setLocale(new Locale(locale));
+        resources.updateConfiguration(config, resources.getDisplayMetrics());
+        return resources.getString(ResourceUtil.getIdentifier(
+                mRemoteContext, name, mWebLayerContext.getPackageName()));
+    }
+
+    private static AssetManager createEmptyAssetManager() throws ReflectiveOperationException {
+        Constructor<AssetManager> constructor = AssetManager.class.getDeclaredConstructor();
+        constructor.setAccessible(true);
+        return constructor.newInstance();
+    }
+
+    private static void addAssetPath(AssetManager assetManager, String path)
+            throws ReflectiveOperationException {
+        Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
+        addAssetPath.invoke(assetManager, path);
+    }
+
+    private static SparseArray<String> getPackageIds(AssetManager assetManager)
+            throws ReflectiveOperationException {
+        Method getAssignedPackageIdentifiers =
+                AssetManager.class.getMethod("getAssignedPackageIdentifiers");
+        return (SparseArray) getAssignedPackageIdentifiers.invoke(assetManager);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ContentCaptureTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ContentCaptureTest.java
new file mode 100644
index 0000000..d9e13c08
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ContentCaptureTest.java
@@ -0,0 +1,116 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Bundle;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Verifies Content Capture works in WebLayer. The feature itself has AwContentCaptureTest.java for
+ * testing its functionality.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ContentCaptureTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static final String MAIN_FRAME_FILE = "/main_frame.html";
+
+    private TestWebServer mWebServer;
+    private InstrumentationActivity mActivity;
+
+    @Before
+    public void setUp() throws Exception {
+        mWebServer = TestWebServer.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mWebServer.shutdown();
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+    }
+
+    /**
+     * Verifies that ContentCapture is working for WebLayer as the TestContentCaptureConsumer is
+     * receiving data from the renderer side.
+     */
+    @Test
+    @SmallTest
+    public void testContentCapture() throws Exception {
+        final String response = "<html><head></head><body>"
+                + "<div id='place_holder'>"
+                + "<p style=\"height: 100vh\">Hello</p>"
+                + "<p>world</p>"
+                + "</body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, response, null);
+
+        ArrayList<Integer> eventsObserved = new ArrayList<>();
+        CallbackHelper helper = new CallbackHelper();
+
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestWebLayer testWebLayer = getTestWebLayer();
+        testWebLayer.addContentCaptureConsumer(
+                mActivity.getBrowser(), () -> helper.notifyCalled(), eventsObserved);
+
+        mActivityTestRule.navigateAndWait(url);
+        helper.waitForFirst();
+
+        Assert.assertEquals(1, eventsObserved.size());
+        Assert.assertEquals(/* CONTENT_CAPTURED*/ Integer.valueOf(1), eventsObserved.get(0));
+    }
+
+    /**
+     * Verifies that ContentCapture doesn't report data for incognito mode.
+     */
+    @Test
+    @SmallTest
+    public void testContentCaptureIncognito() throws Exception {
+        final String response = "<html><head></head><body>"
+                + "<div id='place_holder'>"
+                + "<p style=\"height: 100vh\">Hello</p>"
+                + "<p>world</p>"
+                + "</body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, response, null);
+
+        ArrayList<Integer> eventsObserved = new ArrayList<>();
+        CallbackHelper helper = new CallbackHelper();
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        TestWebLayer testWebLayer = getTestWebLayer();
+        testWebLayer.addContentCaptureConsumer(
+                mActivity.getBrowser(), () -> helper.notifyCalled(), eventsObserved);
+
+        mActivityTestRule.navigateAndWait(url);
+        try {
+            helper.waitForFirst();
+        } catch (TimeoutException e) {
+            // Expecting TimeoutException.
+            return;
+        }
+        Assert.assertTrue("There should be a TimeoutException", false);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CookieManagerTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CookieManagerTest.java
new file mode 100644
index 0000000..e7fc09e
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CookieManagerTest.java
@@ -0,0 +1,264 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.CookieChangeCause;
+import org.chromium.weblayer.CookieChangedCallback;
+import org.chromium.weblayer.CookieManager;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests that CookieManager works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class CookieManagerTest {
+    private CookieManager mCookieManager;
+    private Uri mBaseUri;
+    private Uri mBaseUriWithPath;
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Before
+    public void setUp() {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        mCookieManager = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return activity.getBrowser().getProfile().getCookieManager(); });
+        mBaseUri = Uri.parse(mActivityTestRule.getTestServer().getURL("/"));
+        mBaseUriWithPath = Uri.parse(mActivityTestRule.getTestServer().getURL("/path"));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCookie() throws Exception {
+        Assert.assertTrue(setCookie("foo=bar"));
+
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/echoheader?Cookie"));
+        Assert.assertEquals("foo=bar",
+                mActivityTestRule.executeScriptAndExtractString("document.body.textContent"));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCookieInvalid() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                mCookieManager.setCookie(mBaseUri, "", null);
+                Assert.fail("Exception not thrown.");
+            } catch (IllegalArgumentException e) {
+                Assert.assertEquals(e.getMessage(), "Invalid cookie: ");
+            }
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCookieNotSet() throws Exception {
+        // Attempting to set a Secure cookie from an insecure origin is rejected.
+        // A different hostname must be used because non-cryptographic localhost origins such as
+        // http://127.0.0.1 are considered trustworthy and are allowed to set Secure cookies.
+        Assert.assertFalse(mActivityTestRule.setCookie(
+                mCookieManager, Uri.parse("http://a.test/path"), "foo=bar; Secure"));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCookieNullCallback() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mCookieManager.setCookie(mBaseUri, "foo=bar", null); });
+
+        // Do a navigation to make sure the cookie gets set.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestServer().getURL("/echo"));
+
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/echoheader?Cookie"));
+        Assert.assertEquals("foo=bar",
+                mActivityTestRule.executeScriptAndExtractString("document.body.textContent"));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCookie() throws Exception {
+        Assert.assertEquals(getCookie(), "");
+        Assert.assertTrue(setCookie("foo="));
+        Assert.assertEquals(getCookie(), "foo=");
+        Assert.assertTrue(setCookie("foo=bar"));
+        Assert.assertEquals(getCookie(), "foo=bar");
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    public void testGetResponseCookiesSimple() throws Exception {
+        Assert.assertTrue(getResponseCookies().isEmpty());
+        Assert.assertTrue(setCookie("foo="));
+        Assert.assertThat(getResponseCookies(),
+                Matchers.containsInAnyOrder("foo=; path=/; domain=127.0.0.1; priority=medium"));
+        Assert.assertTrue(setCookie("foo=bar"));
+        Assert.assertThat(getResponseCookies(),
+                Matchers.containsInAnyOrder("foo=bar; path=/; domain=127.0.0.1; priority=medium"));
+
+        Assert.assertTrue(setCookie("baz=blah"));
+        Assert.assertThat(getResponseCookies(),
+                Matchers.containsInAnyOrder("foo=bar; path=/; domain=127.0.0.1; priority=medium",
+                        "baz=blah; path=/; domain=127.0.0.1; priority=medium"));
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    public void testGetResponseCookiesAllAttributes() throws Exception {
+        Assert.assertTrue(getResponseCookies().isEmpty());
+
+        // Setting a cookie with all attributes should return the same cookie.
+        String cookieStart = "foo=bar; path=/; domain=127.0.0.1; expires=";
+        String cookieExpires = "Thu, 15 Jul 2032 00:00:01 GMT";
+        String cookieEnd = "; secure; httponly; samesite=lax; priority=high; sameparty";
+        Assert.assertTrue(setCookie(cookieStart + cookieExpires + cookieEnd));
+        List<String> cookiesSet = getResponseCookies();
+        Assert.assertEquals(cookiesSet.size(), 1);
+        // Expiration is clamped to 400 days, so for now we just test that some date was sent back.
+        Assert.assertTrue(cookiesSet.get(0).matches(cookieStart + "[A-Za-z0-9 ,:]*" + cookieEnd));
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    public void testGetResponseCookiesWithPath() throws Exception {
+        Assert.assertTrue(setCookie(mBaseUriWithPath, "foo=bar; path=/path"));
+        Assert.assertThat(getResponseCookies(mBaseUriWithPath),
+                Matchers.containsInAnyOrder(
+                        "foo=bar; path=/path; domain=127.0.0.1; priority=medium"));
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "Flaky - https://crbug.com/1133891")
+    public void testCookieChanged() throws Exception {
+        CookieChangedCallbackHelper helper = new CookieChangedCallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mCookieManager.addCookieChangedCallback(mBaseUri, null, helper); });
+        Assert.assertTrue(setCookie("foo=bar"));
+        helper.waitForChange();
+        Assert.assertEquals(helper.getCause(), CookieChangeCause.INSERTED);
+        Assert.assertEquals(helper.getCookie(), "foo=bar");
+
+        Assert.assertTrue(setCookie("foo=baz"));
+        helper.waitForChange();
+        Assert.assertEquals(helper.getCause(), CookieChangeCause.OVERWRITE);
+        Assert.assertEquals(helper.getCookie(), "foo=bar");
+        helper.waitForChange();
+        Assert.assertEquals(helper.getCause(), CookieChangeCause.INSERTED);
+        Assert.assertEquals(helper.getCookie(), "foo=baz");
+    }
+
+    @Test
+    @SmallTest
+    public void testCookieChangedRemoveCallback() throws Exception {
+        CookieChangedCallbackHelper helper = new CookieChangedCallbackHelper();
+        Runnable remove = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mCookieManager.addCookieChangedCallback(mBaseUri, "cookie2", helper);
+            return mCookieManager.addCookieChangedCallback(mBaseUri, "cookie1", helper);
+        });
+        Assert.assertTrue(setCookie("cookie1=something"));
+        helper.waitForChange();
+        Assert.assertEquals(helper.getCookie(), "cookie1=something");
+
+        TestThreadUtils.runOnUiThreadBlocking(remove);
+
+        // Set cookie1 first and then cookie2. We should only receive a cookie change event for
+        // cookie2.
+        Assert.assertTrue(setCookie("cookie1=other"));
+        Assert.assertTrue(setCookie("cookie2=something"));
+        helper.waitForChange();
+        Assert.assertEquals(helper.getCookie(), "cookie2=something");
+    }
+
+    @Test
+    @SmallTest
+    public void testCookieChangedRemoveCallbackAfterProfileDestroyed() throws Exception {
+        // Removing change callback should be a no-op after the profile is destroyed.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = mActivityTestRule.getActivity().getBrowser().getProfile();
+            Runnable remove = mCookieManager.addCookieChangedCallback(
+                    mBaseUri, null, new CookieChangedCallbackHelper());
+            // We need to remove the fragment before calling Profile#destroy().
+            FragmentManager fm = mActivityTestRule.getActivity().getSupportFragmentManager();
+            fm.beginTransaction().remove(fm.getFragments().get(0)).commitNow();
+
+            profile.destroy();
+            remove.run();
+        });
+    }
+
+    private boolean setCookie(Uri uri, String value) throws Exception {
+        return mActivityTestRule.setCookie(mCookieManager, uri, value);
+    }
+
+    private boolean setCookie(String value) throws Exception {
+        return setCookie(mBaseUri, value);
+    }
+
+    private String getCookie() throws Exception {
+        return mActivityTestRule.getCookie(mCookieManager, mBaseUri);
+    }
+
+    private List<String> getResponseCookies(Uri uri) throws Exception {
+        return mActivityTestRule.getResponseCookies(mCookieManager, uri);
+    }
+
+    private List<String> getResponseCookies() throws Exception {
+        return getResponseCookies(mBaseUri);
+    }
+
+    private static class CookieChangedCallbackHelper extends CookieChangedCallback {
+        private CallbackHelper mCallbackHelper = new CallbackHelper();
+        private int mCallCount;
+        private int mCause;
+        private String mCookie;
+
+        public void waitForChange() throws TimeoutException {
+            mCallbackHelper.waitForCallback(mCallCount);
+            mCallCount++;
+        }
+
+        @CookieChangeCause
+        public int getCause() {
+            return mCause;
+        }
+
+        public String getCookie() {
+            return mCookie;
+        }
+
+        @Override
+        public void onCookieChanged(String cookie, @CookieChangeCause int cause) {
+            mCookie = cookie;
+            mCause = cause;
+            mCallbackHelper.notifyCalled();
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CrashReporterTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CrashReporterTest.java
new file mode 100644
index 0000000..b5dab58
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/CrashReporterTest.java
@@ -0,0 +1,144 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.CrashReporterCallback;
+import org.chromium.weblayer.CrashReporterController;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Tests for crash reporting in WebLayer.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class CrashReporterTest {
+    private static final String UUID = "032b90a6-836c-49bc-a9f4-aa210458eaf3";
+    private static final String LOCAL_ID = "aa210458eaf3";
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+    private File mCrashReport;
+    private File mCrashSidecar;
+
+    @Before
+    public void setUp() throws IOException {
+        File cacheDir =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir();
+        File crashReportDir = new File(cacheDir, "weblayer/Crash Reports");
+        crashReportDir.mkdirs();
+        mCrashReport = new File(crashReportDir, UUID + ".dmp0.try0");
+        mCrashSidecar = new File(crashReportDir, UUID + ".json");
+        mCrashReport.createNewFile();
+        try (FileOutputStream out = new FileOutputStream(mCrashSidecar)) {
+            out.write("{\"foo\":\"bar\"}".getBytes());
+        }
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        if (mCrashReport.exists()) mCrashReport.delete();
+        if (mCrashSidecar.exists()) mCrashSidecar.delete();
+    }
+
+    private static final class BundleCallbackHelper extends CallbackHelper {
+        private Bundle mResult;
+
+        public Bundle getResult() {
+            return mResult;
+        }
+
+        public void notifyCalled(Bundle result) {
+            mResult = result;
+            notifyCalled();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testCrashReporterLoading() throws Exception {
+        BundleCallbackHelper callbackHelper = new BundleCallbackHelper();
+        CallbackHelper deleteHelper = new CallbackHelper();
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_CREATE_WEBLAYER, false);
+        InstrumentationActivity activity = mActivityTestRule.launchShell(extras);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            CrashReporterController crashReporterController =
+                    CrashReporterController.getInstance(activity);
+            // Set up a callback object that will fetch the crash keys for the crash with id
+            // LOCAL_ID and then delete it.
+            crashReporterController.registerCallback(new CrashReporterCallback() {
+                @Override
+                public void onPendingCrashReports(String[] localIds) {
+                    if (!Arrays.asList(localIds).contains(LOCAL_ID)) {
+                        callbackHelper.notifyFailed("localIds does not contain " + LOCAL_ID);
+                        return;
+                    }
+                    Bundle crashKeys = crashReporterController.getCrashKeys(localIds[0]);
+                    callbackHelper.notifyCalled(crashKeys);
+                    crashReporterController.deleteCrash(localIds[0]);
+                }
+
+                @Override
+                public void onCrashDeleted(String localId) {
+                    deleteHelper.notifyCalled();
+                }
+            });
+
+            // Check for crash reports ready to upload
+            crashReporterController.checkForPendingCrashReports();
+        });
+        // Expect that a Bundle containing { "foo": "bar" } is returned.
+        callbackHelper.waitForFirst();
+        Bundle crashKeys = callbackHelper.getResult();
+        Assert.assertArrayEquals(crashKeys.keySet().toArray(new String[0]), new String[] {"foo"});
+        Assert.assertEquals(crashKeys.getString("foo"), "bar");
+
+        // Expect that the crash report and its sidecar are deleted.
+        deleteHelper.waitForFirst();
+        Assert.assertFalse(mCrashReport.exists());
+        Assert.assertFalse(mCrashSidecar.exists());
+    }
+
+    @MinWebLayerVersion(88) // Fix first appeared in 88.
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1355817")
+    public void testBogusCrashId() throws Exception {
+        CallbackHelper callbackHelper = new CallbackHelper();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            CrashReporterController crashReporterController =
+                    CrashReporterController.getInstance(activity);
+            crashReporterController.registerCallback(new CrashReporterCallback() {
+                @Override
+                public void onCrashUploadFailed(String localId, String message) {
+                    callbackHelper.notifyCalled();
+                }
+            });
+            crashReporterController.uploadCrash("bogus-crash-id");
+        });
+        callbackHelper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DarkModeTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DarkModeTest.java
new file mode 100644
index 0000000..1bae512
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DarkModeTest.java
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.DarkModeStrategy;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that dark mode is handled correctly.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class DarkModeTest {
+    private InstrumentationActivity mActivity;
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private void setDarkModeStrategy(@DarkModeStrategy int darkModeStrategy) {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.loadWebLayerSync(mActivityTestRule.getContextForWebLayer());
+            mActivity.getBrowser().setDarkModeStrategy(darkModeStrategy);
+        });
+    }
+
+    private boolean loadPageAndGetPrefersDark() {
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("dark_mode.html"));
+        return mActivityTestRule.executeScriptAndExtractBoolean(
+                "window.matchMedia('(prefers-color-scheme: dark)').matches");
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testDarkModeWithWebThemeDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.WEB_THEME_DARKENING_ONLY);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertTrue(prefersDark);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testDarkModeWithUserAgentDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.USER_AGENT_DARKENING_ONLY);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertFalse(prefersDark);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testDarkModeWithPreferWebThemeDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertTrue(prefersDark);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testLightModeWithWebThemeDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.WEB_THEME_DARKENING_ONLY);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertFalse(prefersDark);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testLightModeWithUserAgentDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.USER_AGENT_DARKENING_ONLY);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertFalse(prefersDark);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(90)
+    public void testLightModeWithPreferWebThemeDarkening() throws Exception {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+        mActivity = mActivityTestRule.launchShell(new Bundle());
+        setDarkModeStrategy(DarkModeStrategy.PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
+        boolean prefersDark = loadPageAndGetPrefersDark();
+        Assert.assertFalse(prefersDark);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DataClearingTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DataClearingTest.java
new file mode 100644
index 0000000..1519090
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DataClearingTest.java
@@ -0,0 +1,124 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+import static org.chromium.weblayer.BrowsingDataType.CACHE;
+import static org.chromium.weblayer.BrowsingDataType.COOKIES_AND_SITE_DATA;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Example test that just starts the weblayer shell.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class DataClearingTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    public void clearDataWithPersistedProfile_TriggersCallback() {
+        checkTriggersCallbackOnClearData(new int[] {COOKIES_AND_SITE_DATA}, "Profile");
+    }
+
+    @Test
+    @SmallTest
+    public void clearDataWithInMemoryProfile_TriggersCallback() {
+        checkTriggersCallbackOnClearData(new int[] {COOKIES_AND_SITE_DATA}, null);
+    }
+
+    @Test
+    @SmallTest
+    public void clearCacheWithPersistedProfile_TriggersCallback() {
+        checkTriggersCallbackOnClearData(new int[] {CACHE}, "Profile");
+    }
+
+    @Test
+    @SmallTest
+    public void clearCacheWithInMemoryProfile_TriggersCallback() {
+        checkTriggersCallbackOnClearData(new int[] {CACHE}, null);
+    }
+
+    @Test
+    @SmallTest
+    public void clearMultipleTypes_TriggersCallback() {
+        checkTriggersCallbackOnClearData(new int[] {COOKIES_AND_SITE_DATA, CACHE}, "Profile");
+    }
+
+    @Test
+    @SmallTest
+    public void clearUnknownType_TriggersCallback() {
+        // This is a forward compatibility test: the older versions of Chrome that don't yet
+        // implement clearing some data type should just ignore it and call the callback.
+        checkTriggersCallbackOnClearData(new int[] {9999}, "Profile");
+    }
+
+    @Test
+    @SmallTest
+    public void twoSuccesiveRequestsTriggerCallbacks() {
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(2);
+        runOnUiThreadBlocking(() -> {
+            Profile profile = activity.getBrowser().getProfile();
+            profile.clearBrowsingData(new int[] {COOKIES_AND_SITE_DATA}, latch::countDown);
+            profile.clearBrowsingData(new int[] {CACHE}, latch::countDown);
+        });
+        latch.timedAwait();
+    }
+
+    @Test
+    @SmallTest
+    public void clearingAgainAfterClearFinished_TriggersCallback() {
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
+        runOnUiThreadBlocking(() -> {
+            Profile profile = activity.getBrowser().getProfile();
+            profile.clearBrowsingData(new int[] {COOKIES_AND_SITE_DATA},
+                    () -> { profile.clearBrowsingData(new int[] {CACHE}, latch::countDown); });
+        });
+        latch.timedAwait();
+    }
+
+    @Test
+    @SmallTest
+    public void destroyingProfileDuringDataClear_DoesntCrash() {
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
+        runOnUiThreadBlocking(() -> {
+            Profile profile = activity.getBrowser().getProfile();
+            profile.clearBrowsingData(new int[] {COOKIES_AND_SITE_DATA}, () -> {});
+
+            // We need to remove the fragment before calling Profile#destroy().
+            FragmentManager fm = activity.getSupportFragmentManager();
+            fm.beginTransaction().remove(fm.getFragments().get(0)).commitNow();
+
+            profile.destroy();
+            latch.countDown();
+        });
+        latch.timedAwait();
+    }
+
+    private void checkTriggersCallbackOnClearData(int[] dataTypes, String profileName) {
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile(profileName);
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
+        runOnUiThreadBlocking(() -> {
+            activity.getBrowser().getProfile().clearBrowsingData(dataTypes, latch::countDown);
+        });
+        latch.timedAwait();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DisplayCutoutTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DisplayCutoutTest.java
new file mode 100644
index 0000000..5eb8e86
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DisplayCutoutTest.java
@@ -0,0 +1,86 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Build;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that viewport-fit is respected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class DisplayCutoutTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Before
+    public void setUp() {
+        String url = mActivityTestRule.getTestDataURL("display_cutout.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+    }
+
+    @Test
+    @SmallTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.P)
+    public void testWithFullscreen() {
+        Assert.assertEquals(mActivity.getWindow().getAttributes().layoutInDisplayCutoutMode,
+                LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
+
+        // First touch enters fullscreen.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    mActivityTestRule.executeScriptAndExtractBoolean("document.webkitIsFullScreen"),
+                    Matchers.is(true));
+        });
+
+        mActivityTestRule.executeScriptSync("setViewportFit(\"contain\")", false);
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(mActivity.getWindow().getAttributes().layoutInDisplayCutoutMode,
+                    Matchers.is(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER));
+        });
+    }
+
+    @Test
+    @SmallTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.P)
+    public void testWithNoFullscreen() {
+        Assert.assertEquals(mActivity.getWindow().getAttributes().layoutInDisplayCutoutMode,
+                LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
+        Assert.assertFalse(
+                mActivityTestRule.executeScriptAndExtractBoolean("document.webkitIsFullScreen"));
+        mActivityTestRule.executeScriptSync("setViewportFit(\"contain\")", false);
+
+        try {
+            // When not in fullscreen, this criterion will not be fulfilled.
+            CriteriaHelper.pollUiThread(() -> {
+                Criteria.checkThat(mActivity.getWindow().getAttributes().layoutInDisplayCutoutMode,
+                        Matchers.not(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
+            });
+        } catch (AssertionError e) {
+        }
+
+        Assert.assertEquals(mActivity.getWindow().getAttributes().layoutInDisplayCutoutMode,
+                LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DowngradeTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DowngradeTest.java
new file mode 100644
index 0000000..e737eef
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DowngradeTest.java
@@ -0,0 +1,109 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.PathUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Tests that WebLayer version changes handle data correctly.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class DowngradeTest {
+    public static final String PREF_LAST_VERSION_CODE =
+            "org.chromium.weblayer.last_version_code_used";
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    // A test file in the app's data directory. This should never get deleted.
+    private File mAppFile;
+    // A test file in WebLayer's data directory. This should get deleted when we downgrade.
+    private File mWebLayerDataFile;
+
+    @Before
+    public void setUp() throws IOException, PackageManager.NameNotFoundException {
+        PathUtils.setPrivateDataDirectorySuffix("weblayer", "weblayer");
+        mWebLayerDataFile = new File(PathUtils.getDataDirectory(), "testWebLayerFile");
+        assertTrue(mWebLayerDataFile.createNewFile());
+
+        Context context = ContextUtils.getApplicationContext();
+        PackageManager packageManager = context.getPackageManager();
+        PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
+        mAppFile = new File(packageInfo.applicationInfo.dataDir, "testAppFile");
+        assertTrue(mAppFile.createNewFile());
+    }
+
+    @After
+    public void tearDown() {
+        mAppFile.delete();
+        mWebLayerDataFile.delete();
+    }
+
+    @Test
+    @SmallTest
+    public void testDowngradeDeletesData() throws IOException {
+        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+        prefs.edit().putInt(PREF_LAST_VERSION_CODE, 9999_000_00).apply();
+
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+        runOnUiThreadBlocking(
+                () -> { activity.loadWebLayerSync(ContextUtils.getApplicationContext()); });
+
+        assertFalse(mWebLayerDataFile.exists());
+        assertTrue(mAppFile.exists());
+    }
+
+    @Test
+    @SmallTest
+    public void testUnknownLastVersionKeepsData() throws IOException {
+        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+        assertFalse(prefs.contains(PREF_LAST_VERSION_CODE));
+
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+        runOnUiThreadBlocking(
+                () -> { activity.loadWebLayerSync(ContextUtils.getApplicationContext()); });
+
+        assertTrue(mWebLayerDataFile.exists());
+        assertTrue(mAppFile.exists());
+    }
+
+    @Test
+    @SmallTest
+    public void testNewVersionKeepsData() {
+        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+        prefs.edit().putInt(PREF_LAST_VERSION_CODE, 1_000_00).apply();
+
+        InstrumentationActivity activity = mActivityTestRule.launchWithProfile("profile");
+        runOnUiThreadBlocking(
+                () -> { activity.loadWebLayerSync(ContextUtils.getApplicationContext()); });
+
+        assertTrue(mWebLayerDataFile.exists());
+        assertTrue(mAppFile.exists());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java
new file mode 100644
index 0000000..06e07fa
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java
@@ -0,0 +1,252 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.util.Pair;
+import android.webkit.ValueCallback;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Download;
+import org.chromium.weblayer.DownloadCallback;
+import org.chromium.weblayer.DownloadError;
+import org.chromium.weblayer.DownloadState;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that the DownloadCallback method is invoked for downloads.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class DownloadCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private Callback mCallback;
+
+    private static class Callback extends DownloadCallback {
+        public String mUrl;
+        public String mUserAgent;
+        public String mContentDisposition;
+        public String mMimetype;
+        public String mLocation;
+        public String mFileName;
+        public @DownloadState int mState;
+        public @DownloadError int mError;
+        public long mContentLength;
+        public boolean mIntercept;
+        public boolean mSeenStarted;
+        public boolean mSeenCompleted;
+        public boolean mSeenFailed;
+
+        @Override
+        public boolean onInterceptDownload(Uri uri, String userAgent, String contentDisposition,
+                String mimetype, long contentLength) {
+            mUrl = uri.toString();
+            mUserAgent = userAgent;
+            mContentDisposition = contentDisposition;
+            mMimetype = mimetype;
+            mContentLength = contentLength;
+            return mIntercept;
+        }
+
+        @Override
+        public void allowDownload(Uri uri, String requestMethod, Uri requestInitiator,
+                ValueCallback<Boolean> callback) {
+            callback.onReceiveValue(true);
+        }
+
+        @Override
+        public void onDownloadStarted(Download download) {
+            mSeenStarted = true;
+            download.disableNotification();
+        }
+
+        @Override
+        public void onDownloadCompleted(Download download) {
+            mSeenCompleted = true;
+            mLocation = download.getLocation().toString();
+            mFileName = download.getFileNameToReportToUser().toString();
+            mState = download.getState();
+            mError = download.getError();
+            mMimetype = download.getMimeType();
+        }
+
+        @Override
+        public void onDownloadFailed(Download download) {
+            mSeenFailed = true;
+            mState = download.getState();
+            mError = download.getError();
+        }
+
+        public void waitForIntercept() {
+            CriteriaHelper.pollInstrumentationThread(
+                    () -> Criteria.checkThat(mUrl, Matchers.notNullValue()));
+        }
+
+        public void waitForStarted() {
+            CriteriaHelper.pollInstrumentationThread(() -> mSeenStarted);
+        }
+
+        public void waitForCompleted() {
+            CriteriaHelper.pollInstrumentationThread(() -> mSeenCompleted);
+        }
+
+        public void waitForFailed() {
+            CriteriaHelper.pollInstrumentationThread(() -> mSeenFailed);
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.launchShellWithUrl(null);
+        Assert.assertNotNull(mActivity);
+
+        // Don't fill up the default download directory on the device.
+        String tempDownloadDirectory =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir()
+                + "/weblayer/Downloads";
+
+        mCallback = new Callback();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = mActivity.getBrowser().getProfile();
+            profile.setDownloadCallback(mCallback);
+            profile.setDownloadDirectory(new File(tempDownloadDirectory));
+        });
+    }
+
+    /**
+     * Verifies the DownloadCallback is informed of downloads resulting from navigations to pages
+     * with Content-Disposition attachment.
+     */
+    @Test
+    @SmallTest
+    public void testInterceptDownloadByContentDisposition() throws Throwable {
+        mCallback.mIntercept = true;
+        final String data = "download data";
+        final String contentDisposition = "attachment;filename=\"download.txt\"";
+        final String mimetype = "text/plain";
+
+        List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
+        downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
+        downloadHeaders.add(Pair.create("Content-Type", mimetype));
+        downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
+
+        TestWebServer webServer = TestWebServer.start();
+        try {
+            final String pageUrl = webServer.setResponse("/download.txt", data, downloadHeaders);
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mActivity.getTab().getNavigationController().navigate(Uri.parse(pageUrl));
+            });
+            mCallback.waitForIntercept();
+
+            Assert.assertEquals(pageUrl, mCallback.mUrl);
+            Assert.assertEquals(contentDisposition, mCallback.mContentDisposition);
+            Assert.assertEquals(mimetype, mCallback.mMimetype);
+            Assert.assertEquals(data.length(), mCallback.mContentLength);
+            // TODO(estade): verify mUserAgent.
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
+    /**
+     * Verifies that if the first navigation in a Tab is for a download then it is deleted.
+     */
+    @Test
+    @SmallTest
+    public void testFirstNavigationIsDownloadClosesTab() throws Throwable {
+        // Set up listening for the tab removal that we expect to happen.
+        CallbackHelper onTabRemovedCallbackHelper = new CallbackHelper();
+        TabListCallback tabListCallback = new TabListCallback() {
+            @Override
+            public void onTabRemoved(Tab tab) {
+                onTabRemovedCallbackHelper.notifyCalled();
+            }
+        };
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.registerTabListCallback(tabListCallback); });
+
+        final String data = "download data";
+        final String contentDisposition = "attachment;filename=\"download.txt\"";
+        final String mimetype = "text/plain";
+
+        List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
+        downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
+        downloadHeaders.add(Pair.create("Content-Type", mimetype));
+        downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
+
+        TestWebServer webServer = TestWebServer.start();
+        try {
+            final String pageUrl = webServer.setResponse("/download.txt", data, downloadHeaders);
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mActivity.getTab().getNavigationController().navigate(Uri.parse(pageUrl));
+            });
+            mCallback.waitForCompleted();
+            onTabRemovedCallbackHelper.waitForFirst();
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
+    /**
+     * Verifies the DownloadCallback is informed of downloads resulting from the user clicking on a
+     * download link.
+     */
+    @Test
+    @SmallTest
+    public void testInterceptDownloadByLinkAttribute() {
+        mCallback.mIntercept = true;
+        String pageUrl = mActivityTestRule.getTestDataURL("download.html");
+        mActivityTestRule.navigateAndWait(pageUrl);
+
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mCallback.waitForIntercept();
+        Assert.assertEquals(mActivityTestRule.getTestDataURL("lorem_ipsum.txt"), mCallback.mUrl);
+    }
+
+    @Test
+    @SmallTest
+    public void testBasic() {
+        String url = mActivityTestRule.getTestDataURL("content-disposition.html");
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+        mCallback.waitForStarted();
+        mCallback.waitForCompleted();
+
+        // Location varies depending upon version. See PathUtils.getDownloadsDirectory().
+        Assert.assertTrue(mCallback.mLocation.contains(
+                                  "org.chromium.weblayer.shell/cache/weblayer/Downloads/")
+                || mCallback.mLocation.contains("/media/"));
+        Assert.assertTrue(mCallback.mFileName.contains("test"));
+        Assert.assertEquals(DownloadState.COMPLETE, mCallback.mState);
+        Assert.assertEquals(DownloadError.NO_ERROR, mCallback.mError);
+        Assert.assertEquals("text/html", mCallback.mMimetype);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ErrorPageCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ErrorPageCallbackTest.java
new file mode 100644
index 0000000..a47ba33a2
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ErrorPageCallbackTest.java
@@ -0,0 +1,166 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.ErrorPage;
+import org.chromium.weblayer.ErrorPageCallback;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationController;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that ErrorPageCallback works as expected for handling error page interactions.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ErrorPageCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+    private InstrumentationActivity mActivity;
+    // Only one EmbeddedTestServer may be used at a time.
+    private TestWebServer mGoodServer;
+    private EmbeddedTestServer mBadSslServer;
+    private String mGoodUrl;
+    private String mBadUrl;
+    private Callback mCallback;
+
+    private static class Callback extends ErrorPageCallback {
+        public boolean mSignaled;
+        public String mSafetyPage;
+        public ErrorPage mErrorPage;
+        public Tab mTab;
+
+        public Callback(Tab tab) {
+            mTab = tab;
+        }
+
+        @Override
+        public boolean onBackToSafety() {
+            mSignaled = true;
+            if (mSafetyPage == null) {
+                return false;
+            }
+
+            mTab.getNavigationController().navigate(Uri.parse(mSafetyPage));
+            return true;
+        }
+
+        @Override
+        public ErrorPage getErrorPage(Navigation navigation) {
+            return mErrorPage;
+        }
+    }
+
+    @Before
+    public void setUp() throws Throwable {
+        mActivity = mActivityTestRule.launchShellWithUrl(null);
+        Assert.assertNotNull(mActivity);
+
+        mGoodServer = TestWebServer.start();
+        mGoodUrl = mGoodServer.setResponse("/ok.html", "<html>ok</html>", null);
+
+        mBadSslServer = EmbeddedTestServer.createAndStartHTTPSServer(
+                mActivity, ServerCertificate.CERT_MISMATCHED_NAME);
+        mBadUrl = mBadSslServer.getURL("/weblayer/test/data/simple_page.html");
+
+        mCallback = new Callback(mActivity.getTab());
+
+        mActivityTestRule.navigateAndWait(mGoodUrl);
+        mActivityTestRule.navigateAndWaitForFailure(mBadUrl);
+    }
+
+    @After
+    public void tearDown() {
+        mBadSslServer.stopAndDestroyServer();
+    }
+
+    /**
+     * Verifies that if there's no ErrorPageCallback, when the user clicks "back to safety",
+     * WebLayer provides default behavior (navigating back).
+     */
+    @Test
+    @SmallTest
+    public void testBackToSafetyDefaultBehavior() throws Throwable {
+        NavigationWaiter navigationWaiter = new NavigationWaiter(
+                mGoodUrl, mActivity.getTab(), false /* expectFailure */, true /* waitForPaint */);
+        mActivityTestRule.executeScriptSync(
+                "window.certificateErrorPageController.dontProceed();", false);
+        navigationWaiter.waitForNavigation();
+        Assert.assertFalse(mCallback.mSignaled);
+    }
+
+    /**
+     * Verifies that if there's an ErrorPageCallback and onBackToSafety returns true, WebLayer does
+     * *not* provide default behavior.
+     */
+    @Test
+    @SmallTest
+    public void testBackToSafetyOverride() throws Throwable {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().setErrorPageCallback(mCallback); });
+
+        mCallback.mSafetyPage = mGoodServer.setResponse("/safe.html", "<html>safe</html>", null);
+
+        NavigationWaiter navigationWaiter = new NavigationWaiter(mCallback.mSafetyPage,
+                mActivity.getTab(), false /* expectFailure */, true /* waitForPaint */);
+        mActivityTestRule.executeScriptSync(
+                "window.certificateErrorPageController.dontProceed();", false);
+        navigationWaiter.waitForNavigation();
+        Assert.assertTrue(mCallback.mSignaled);
+    }
+
+    /**
+     * Verifies that if there's an ErrorPageCallback and onBackToSafety returns false, WebLayer
+     * *does* provide default behavior.
+     */
+    @Test
+    @SmallTest
+    public void testBackToSafetyDontOverride() throws Throwable {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().setErrorPageCallback(mCallback); });
+
+        NavigationWaiter navigationWaiter = new NavigationWaiter(
+                mGoodUrl, mActivity.getTab(), false /* expectFailure */, true /* waitForPaint */);
+        mActivityTestRule.executeScriptSync(
+                "window.certificateErrorPageController.dontProceed();", false);
+        navigationWaiter.waitForNavigation();
+        Assert.assertTrue(mCallback.mSignaled);
+    }
+
+    @Test
+    @SmallTest
+    public void testOverrideErrorPage() throws Throwable {
+        mCallback.mErrorPage = new ErrorPage("<html><head><title>test error</title>");
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().setErrorPageCallback(mCallback); });
+        String errorPageUrl = "http://localhost:7/non_existent";
+        mActivityTestRule.navigateAndWaitForFailure(errorPageUrl);
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController =
+                    mActivity.getTab().getNavigationController();
+            Assert.assertEquals("test error",
+                    navigationController.getNavigationEntryTitle(
+                            navigationController.getNavigationListCurrentIndex()));
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/EventUtils.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/EventUtils.java
new file mode 100644
index 0000000..21589873
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/EventUtils.java
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Utilities related to event generation.
+ */
+public final class EventUtils {
+    private EventUtils() {}
+
+    /**
+     * Asynchronously posts a touch-down and touch-up event at the center of the supplied View.
+     */
+    public static void simulateTouchCenterOfView(final View view) {
+        simulateDragFromCenterOfView(view, 0, 0);
+    }
+
+    /**
+     * Asynchronously posts a touch-down and touch-up event at the center of the supplied View. If
+     * deltaX or deltaY is non-zero a touch-move is generated between the down/up. Each individual
+     * event is posted asynchronously wrt the other events, which is necessary for scrolling events
+     * to be fully processed by //content and ripple out to observers (e.g., the infobar container).
+     */
+    public static void simulateDragFromCenterOfView(
+            final View view, final float deltaX, final float deltaY) {
+        view.post(() -> {
+            long eventTime = SystemClock.uptimeMillis();
+            float x = (float) (view.getRight() - view.getLeft()) / 2;
+            float y = (float) (view.getBottom() - view.getTop()) / 2;
+            view.dispatchTouchEvent(
+                    MotionEvent.obtain(eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0));
+            view.post(() -> {
+                long newEventTime = SystemClock.uptimeMillis();
+                if (deltaX != 0 || deltaY != 0) {
+                    view.dispatchTouchEvent(MotionEvent.obtain(newEventTime, newEventTime,
+                            MotionEvent.ACTION_MOVE, x + deltaX, y + deltaY, 0));
+                }
+                view.post(() -> {
+                    long newestEventTime = SystemClock.uptimeMillis();
+                    view.dispatchTouchEvent(MotionEvent.obtain(newestEventTime, newestEventTime,
+                            MotionEvent.ACTION_UP, x + deltaX, y + deltaY, 0));
+                });
+            });
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExecuteScriptTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExecuteScriptTest.java
new file mode 100644
index 0000000..18bde07
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExecuteScriptTest.java
@@ -0,0 +1,93 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that script execution works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ExecuteScriptTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static final String DATA_URL = UrlUtils.encodeHtmlDataUri(
+            "<html><head><script>var bar = 10;</script></head><body>foo</body></html>");
+
+    @Test
+    @SmallTest
+    public void testBasicScript() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        JSONObject result = mActivityTestRule.executeScriptSync(
+                "document.body.innerHTML", true /* useSeparateIsolate */);
+        Assert.assertEquals(
+                result.getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), "foo");
+    }
+
+    @Test
+    @SmallTest
+    public void testScriptIsolatedFromPage() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        JSONObject result =
+                mActivityTestRule.executeScriptSync("bar", true /* useSeparateIsolate */);
+        Assert.assertTrue(result.isNull(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
+    }
+
+    @Test
+    @SmallTest
+    public void testMainWorldScriptNotIsolatedFromPage() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        JSONObject result =
+                mActivityTestRule.executeScriptSync("bar", false /* useSeparateIsolate */);
+        Assert.assertEquals(result.getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), 10);
+    }
+
+    @Test
+    @SmallTest
+    public void testScriptNotIsolatedFromOtherScript() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        mActivityTestRule.executeScriptSync("var foo = 20;", true /* useSeparateIsolate */);
+        JSONObject result =
+                mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */);
+        Assert.assertEquals(result.getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY), 20);
+    }
+
+    @Test
+    @SmallTest
+    public void testClearedOnNavigate() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        mActivityTestRule.executeScriptSync("var foo = 20;", true /* useSeparateIsolate */);
+
+        String newUrl = UrlUtils.encodeHtmlDataUri("<html></html>");
+        mActivityTestRule.navigateAndWait(newUrl);
+        JSONObject result =
+                mActivityTestRule.executeScriptSync("foo", true /* useSeparateIsolate */);
+        Assert.assertTrue(result.isNull(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
+    }
+
+    @Test
+    @SmallTest
+    public void testNullCallback() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(DATA_URL);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Null callback should not crash.
+            activity.getTab().executeScript("null", true /* useSeparateIsolate */, null);
+        });
+        // Execute a sync script to make sure the other script finishes.
+        mActivityTestRule.executeScriptSync("null", true /* useSeparateIsolate */);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
new file mode 100644
index 0000000..063d56c
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
@@ -0,0 +1,1578 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Callback;
+import org.chromium.weblayer.ExternalIntentInIncognitoCallback;
+import org.chromium.weblayer.ExternalIntentInIncognitoUserDecision;
+import org.chromium.weblayer.NavigateParams;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests handling of external intents.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ExternalNavigationTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static final boolean EXPECT_NAVIGATION_COMPLETION = true;
+    private static final boolean EXPECT_NAVIGATION_FAILURE = false;
+    private static final boolean RESULTS_IN_EXTERNAL_INTENT = true;
+    private static final boolean DOESNT_RESULT_IN_EXTERNAL_INTENT = false;
+    private static final boolean RESULTS_IN_USER_DECIDING_EXTERNAL_INTENT = true;
+    private static final boolean DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT = false;
+
+    private static final String ABOUT_BLANK_URL = "about:blank";
+    private static final String CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER =
+            "weblayer://weblayertest/intent";
+    private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING =
+            CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER;
+    private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_ACTION =
+            "android.intent.action.VIEW";
+    // The package is not specified in the intent that gets created when navigating to the special
+    // scheme.
+    private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_PACKAGE = null;
+    private static final String INTENT_TO_SELF_DATA_CONTENT = "example.test";
+    private static final String INTENT_TO_SELF_SCHEME = "https";
+    private static final String INTENT_TO_SELF_DATA_STRING =
+            INTENT_TO_SELF_SCHEME + "://" + INTENT_TO_SELF_DATA_CONTENT;
+    private static final String INTENT_TO_SELF_ACTION = "android.intent.action.VIEW";
+    private static final String INTENT_TO_SELF_PACKAGE =
+            InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
+
+    // An intent that opens the test app to view a specified URL. Note that the "end" is left off to
+    // allow appending extras when constructing URLs.
+    private static final String INTENT_TO_SELF = "intent://" + INTENT_TO_SELF_DATA_CONTENT
+            + "#Intent;scheme=" + INTENT_TO_SELF_SCHEME + ";action=" + INTENT_TO_SELF_ACTION
+            + ";package=" + INTENT_TO_SELF_PACKAGE + ";";
+    private static final String INTENT_TO_SELF_URL = INTENT_TO_SELF + "end";
+
+    // An intent URL that gets rejected as malformed.
+    private static final String MALFORMED_INTENT_URL = "intent://garbage;end";
+
+    // An intent that is properly formed but wishes to open an app that is not present on the
+    // device. Note that the "end" is left off to allow appending extras when constructing URLs.
+    private static final String NON_RESOLVABLE_INTENT =
+            "intent://dummy.com/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.missing.app;";
+
+    private static final String LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE =
+            "link_with_intent_to_package_in_same_tab.html#" + INTENT_TO_SELF_PACKAGE;
+    private static final String LINK_WITH_INTENT_TO_SELF_IN_NEW_TAB_FILE =
+            "link_with_intent_to_package_in_new_tab.html#" + INTENT_TO_SELF_PACKAGE;
+    private static final String PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE =
+            "page_that_intents_to_package_on_load.html#" + INTENT_TO_SELF_PACKAGE;
+    private static final String LINK_TO_PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE =
+            "link_to_page_that_intents_to_package_on_load.html#" + INTENT_TO_SELF_PACKAGE;
+
+    // The test server handles "echo" with a response containing "Echo" :).
+    private final String mTestServerSiteUrl = mActivityTestRule.getTestServer().getURL("/echo");
+
+    private final String mTestServerSiteFallbackUrlExtra =
+            "S.browser_fallback_url=" + android.net.Uri.encode(mTestServerSiteUrl) + ";";
+    private final String mIntentToSelfWithFallbackUrl =
+            INTENT_TO_SELF + mTestServerSiteFallbackUrlExtra + "end";
+    private final String mNonResolvableIntentWithFallbackUrl =
+            NON_RESOLVABLE_INTENT + mTestServerSiteFallbackUrlExtra + "end";
+
+    private final String mRedirectToCustomSchemeUrlWithDefaultExternalHandler =
+            mActivityTestRule.getTestServer().getURL(
+                    "/server-redirect?" + CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER);
+    private final String mRedirectToIntentToSelfURL =
+            mActivityTestRule.getTestServer().getURL("/server-redirect?" + INTENT_TO_SELF_URL);
+    private final String mNonResolvableIntentWithFallbackUrlThatLaunchesIntent =
+            NON_RESOLVABLE_INTENT + "S.browser_fallback_url="
+            + android.net.Uri.encode(mRedirectToIntentToSelfURL) + ";end";
+
+    private static final String SPECIALIZED_DATA_URL = "data://externalnavtest";
+
+    private class IntentInterceptor implements InstrumentationActivity.IntentInterceptor {
+        public Intent mLastIntent;
+        private CallbackHelper mCallbackHelper = new CallbackHelper();
+
+        @Override
+        public void interceptIntent(Intent intent, int requestCode, Bundle options) {
+            mLastIntent = intent;
+            mCallbackHelper.notifyCalled();
+        }
+
+        public void waitForIntent() {
+            try {
+                mCallbackHelper.waitForFirst();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private class ExternalIntentInIncognitoCallbackTestImpl
+            extends ExternalIntentInIncognitoCallback {
+        private Callback<Integer> mOnUserDecisionCallback;
+        CallbackHelper mCallbackHelper = new CallbackHelper();
+
+        @Override
+        public void onExternalIntentInIncognito(@NonNull Callback<Integer> onUserDecisionCallback) {
+            mOnUserDecisionCallback = onUserDecisionCallback;
+            mCallbackHelper.notifyCalled();
+        }
+
+        public Callback<Integer> getOnUserDecisionCallback() {
+            return mOnUserDecisionCallback;
+        }
+
+        public void waitForNotificationOnExternalIntentLaunch() throws Throwable {
+            mCallbackHelper.waitForFirst();
+        }
+    }
+
+    /*
+     * Navigates to |urlToNavigateTo| and waits for a completed/failed navigation to |urlToWaitFor|
+     * as appropriate. In the callback verifies that the values of the relevant params on the
+     * Navigation match the passed-in expected values.
+     */
+    private void navigateAndCheckExternalIntentParams(String urlToNavigateTo, String urlToWaitFor,
+            boolean expectNavigationCompletion, boolean resultsInExternalIntent,
+            boolean resultsInUserDecidingIntentLaunch) throws Throwable {
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        CallbackHelper navigationCompletedCallbackHelper = new CallbackHelper();
+        CallbackHelper navigationFailedCallbackHelper = new CallbackHelper();
+
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationCompleted(Navigation navigation) {
+                String url = navigation.getUri().toString();
+                if (!url.equals(urlToWaitFor)) return;
+
+                Assert.assertEquals(true, expectNavigationCompletion);
+
+                // A navigation should never be expected to both complete and result in an external
+                // intent.
+                Assert.assertEquals(false, resultsInExternalIntent);
+                Assert.assertEquals(false, navigation.wasIntentLaunched());
+                Assert.assertEquals(false, resultsInUserDecidingIntentLaunch);
+                Assert.assertEquals(false, navigation.isUserDecidingIntentLaunch());
+
+                navigationCompletedCallbackHelper.notifyCalled();
+            }
+
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                String url = navigation.getUri().toString();
+                if (!url.equals(urlToWaitFor)) return;
+
+                Assert.assertEquals(false, expectNavigationCompletion);
+
+                Assert.assertEquals(resultsInExternalIntent, navigation.wasIntentLaunched());
+                Assert.assertEquals(
+                        resultsInUserDecidingIntentLaunch, navigation.isUserDecidingIntentLaunch());
+
+                navigationFailedCallbackHelper.notifyCalled();
+            }
+        };
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+            tab.getNavigationController().navigate(Uri.parse(urlToNavigateTo));
+        });
+
+        if (expectNavigationCompletion) {
+            navigationCompletedCallbackHelper.waitForFirst();
+        } else {
+            navigationFailedCallbackHelper.waitForFirst();
+        }
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /*
+     * A convenience variant of the above method that navigates to and waits for the same URL. See
+     * comments on the above method.
+     */
+    private void navigateAndCheckExternalIntentParams(String urlToNavigateTo,
+            boolean expectNavigationCompletion, boolean resultsInExternalIntent,
+            boolean resultsInUserDecidingIntentLaunch) throws Throwable {
+        navigateAndCheckExternalIntentParams(urlToNavigateTo, urlToNavigateTo,
+                expectNavigationCompletion, resultsInExternalIntent,
+                resultsInUserDecidingIntentLaunch);
+    }
+
+    /**
+     * Verifies that for a navigation to a URI that WebLayer can handle internally, there
+     * is no external intent triggered.
+     */
+    @Test
+    @SmallTest
+    public void testBrowserNavigation() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        mActivityTestRule.navigateAndWait(mTestServerSiteUrl);
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+        Assert.assertEquals(mTestServerSiteUrl, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in a background tab is blocked.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentWithNoRedirectInBackgroundTabBlockedByDefault() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab backgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.getTab().getBrowser().createTab());
+        Tab activeTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return activity.getTab().getBrowser().getActiveTab(); });
+        Assert.assertNotEquals(backgroundTab, activeTab);
+
+        // Navigate directly to an intent in the background and verify that the intent is not
+        // launched.
+        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_SELF_URL, backgroundTab,
+                /*expectFailure=*/true, /*waitForPaint=*/false);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            backgroundTab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL));
+        });
+
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+        int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
+        Assert.assertEquals(0, numNavigationsInBackgroundTab);
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in a background tab is launched when
+     * intent launches are allowed in the background for this navigation.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void
+    testExternalIntentWithNoRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab backgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.getTab().getBrowser().createTab());
+        Tab activeTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return activity.getTab().getBrowser().getActiveTab(); });
+        Assert.assertNotEquals(backgroundTab, activeTab);
+
+        // Put a initial navigation in the background tab to ease verification of state
+        // afterward (note that this navigation will not result in a paint due to the tab being in
+        // the background).
+        mActivityTestRule.navigateAndWait(backgroundTab, ABOUT_BLANK_URL, /*waitForPaint=*/false);
+
+        // Navigate directly to an intent in the background tab with intent launching in the
+        // background allowed and verify that the intent is launched.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+            navigateParamsBuilder.allowIntentLaunchesInBackground();
+            backgroundTab.getNavigationController().navigate(
+                    Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
+        });
+
+        intentInterceptor.waitForIntent();
+
+        // The intent should have been launched, and there should still be only the initial
+        // navigation in the background tab.
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
+        Assert.assertEquals(1, numNavigationsInBackgroundTab);
+    }
+
+    /**
+     * Tests that a redirect to an external intent in a background tab is blocked.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentAfterRedirectInBackgroundTabBlockedByDefault() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab backgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.getTab().getBrowser().createTab());
+        Tab activeTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return activity.getTab().getBrowser().getActiveTab(); });
+        Assert.assertNotEquals(backgroundTab, activeTab);
+
+        // Perform a navigation that redirects to an intent in the background and verify that the
+        // intent is not launched.
+        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_SELF_URL, backgroundTab,
+                /*expectFailure=*/true, /*waitForPaint=*/false);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            backgroundTab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
+        });
+
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+        int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
+        Assert.assertEquals(0, numNavigationsInBackgroundTab);
+    }
+
+    /**
+     * Tests that a redirect to an external intent in a background tab is launched when
+     * intent launches are allowed in the background for this navigation.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void
+    testExternalIntentAfterRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab backgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.getTab().getBrowser().createTab());
+        Tab activeTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return activity.getTab().getBrowser().getActiveTab(); });
+        Assert.assertNotEquals(backgroundTab, activeTab);
+
+        // Put a initial navigation in the background tab to ease verification of state
+        // afterward (note that this navigation will not result in a paint due to the tab being in
+        // the background).
+        mActivityTestRule.navigateAndWait(backgroundTab, ABOUT_BLANK_URL, /*waitForPaint=*/false);
+
+        // Perform a navigation that redirects to an intent in the background tab with intent
+        // launching in the background allowed and verify that the intent is launched.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+            navigateParamsBuilder.allowIntentLaunchesInBackground();
+            backgroundTab.getNavigationController().navigate(
+                    Uri.parse(mRedirectToIntentToSelfURL), navigateParamsBuilder.build());
+        });
+
+        intentInterceptor.waitForIntent();
+
+        // The intent should have been launched, and there should still be only the initial
+        // navigation in the background tab.
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
+        Assert.assertEquals(1, numNavigationsInBackgroundTab);
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup is blocked as the
+     * browser is not yet attached to the window at the time of the navigation and thus the tab is
+     * not visible.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentWithNoRedirectInBrowserStartupBlockedByDefault()
+            throws Throwable {
+        CallbackHelper onNavigationFailedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
+                    onNavigationFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        // The flow being tested is where the navigation occurs synchronously with initial browser
+        // creation.
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        activity.setIntentInterceptor(intentInterceptor);
+                        browser.getActiveTab().getNavigationController().registerNavigationCallback(
+                                navigationCallback);
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_SELF_URL));
+                    }
+                });
+
+        mActivityTestRule.launchShell(new Bundle());
+
+        // The navigation should fail...
+        onNavigationFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched...
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // ...and there should be one tab in the browser without any navigations in it.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(1, numTabs);
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(0, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            browser.getActiveTab().getNavigationController().unregisterNavigationCallback(
+                    navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup is launched if the
+     * embedder specifies that intent launches in the background are allowed for this navigation.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void
+    testExternalIntentWithNoRedirectInBrowserStartupLaunchedWhenBackgroundLaunchesAllowed()
+            throws Throwable {
+        CallbackHelper onTabRemovedCallbackHelper = new CallbackHelper();
+        TabListCallback tabListCallback = new TabListCallback() {
+            @Override
+            public void onTabRemoved(Tab tab) {
+                onTabRemovedCallbackHelper.notifyCalled();
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        activity.setIntentInterceptor(intentInterceptor);
+                        browser.registerTabListCallback(tabListCallback);
+
+                        NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+                        navigateParamsBuilder.allowIntentLaunchesInBackground();
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
+                    }
+                });
+
+        mActivityTestRule.launchShell(new Bundle());
+
+        // The intent should be launched...
+        intentInterceptor.waitForIntent();
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        // ...the tab created for the initial navigation should be closed...
+        onTabRemovedCallbackHelper.waitForFirst();
+
+        // ...and there should now be no tabs in the browser.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(0, numTabs);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.unregisterTabListCallback(tabListCallback); });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup in incognito mode is
+     * blocked as the browser is not yet attached to the window at the time of the navigation and
+     * thus the tab is not visible.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentWithNoRedirectOnBrowserStartupInIncognitoBlockedByDefault()
+            throws Throwable {
+        CallbackHelper onNavigationFailedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
+                    onNavigationFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        Assert.assertEquals(true, browser.getProfile().isIncognito());
+                        activity.setIntentInterceptor(intentInterceptor);
+                        browser.getActiveTab().getNavigationController().registerNavigationCallback(
+                                navigationCallback);
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_SELF_URL));
+                    }
+                });
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShell(extras);
+
+        // The navigation should fail...
+        onNavigationFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched...
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // ...and there should be one tab in the browser without any navigations in it.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(1, numTabs);
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(0, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            browser.getActiveTab().getNavigationController().unregisterNavigationCallback(
+                    navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup in incognito mode
+     * causes an alert dialog to be shown if the embedder specifies that intent launches in the
+     * background are allowed for this navigation, and that it is then launched if the user consents
+     * via the dialog.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void
+    testExternalIntentWithNoRedirectInBrowserStartupInIncognitoLaunchedWhenBackgroundLaunchesAllowedAndUserConsents()
+            throws Throwable {
+        CallbackHelper onTabRemovedCallbackHelper = new CallbackHelper();
+        TabListCallback tabListCallback = new TabListCallback() {
+            @Override
+            public void onTabRemoved(Tab tab) {
+                onTabRemovedCallbackHelper.notifyCalled();
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        Assert.assertEquals(true, browser.getProfile().isIncognito());
+                        activity.setIntentInterceptor(intentInterceptor);
+                        browser.registerTabListCallback(tabListCallback);
+
+                        NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+                        navigateParamsBuilder.allowIntentLaunchesInBackground();
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
+                    }
+                });
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShell(extras);
+
+        // The alert dialog notifying the user that they are about to leave incognito should pop up.
+        // Click the AlertDialog positive button (button1) when it does.
+        onView(withId(android.R.id.button1))
+                .check(matches(withText("Leave")))
+                .check(matches(isDisplayed()))
+                .perform(click());
+
+        // The intent should be launched...
+        intentInterceptor.waitForIntent();
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        // ...the tab created for the initial navigation should be closed...
+        onTabRemovedCallbackHelper.waitForFirst();
+
+        // ...and there should now be no tabs in the browser.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(0, numTabs);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.unregisterTabListCallback(tabListCallback); });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup in incognito mode
+     * causes an alert dialog to be shown if the embedder specifies that intent launches in the
+     * background are allowed for this navigation, and that it is blocked if the user forbids it via
+     * the dialog.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(92)
+    public void
+    testExternalIntentWithNoRedirectInBrowserStartupInIncognitoBlockedWhenBackgroundLaunchesAllowedAndUserForbids()
+            throws Throwable {
+        CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
+        CallbackHelper onNavigationToIntentDataStringStartedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                // There should be no additional navigations after the initial one.
+                Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING,
+                        navigation.getUri().toString());
+            }
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(
+                            INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING)) {
+                    onNavigationToIntentFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        activity.setIntentInterceptor(intentInterceptor);
+                        Assert.assertEquals(true, browser.getProfile().isIncognito());
+                        browser.getActiveTab().getNavigationController().registerNavigationCallback(
+                                navigationCallback);
+
+                        NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+                        navigateParamsBuilder.allowIntentLaunchesInBackground();
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING),
+                                navigateParamsBuilder.build());
+                    }
+                });
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShell(extras);
+
+        // The alert dialog notifying the user that they are about to leave incognito should pop up.
+        // Click the AlertDialog negative button (button2) when it does.
+        onView(withId(android.R.id.button2))
+                .check(matches(withText("Stay")))
+                .check(matches(isDisplayed()))
+                .perform(click());
+
+        // The navigation should fail...
+        onNavigationToIntentFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched.
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // As there was no fallback Url, there should be zero navigations in the tab.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(0, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            browser.getActiveTab().getNavigationController().unregisterNavigationCallback(
+                    navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup in incognito mode
+     * with the embedder having set an ExternalIntentInIncognitoCallback instance causes that
+     * instance to be notified if the embedder specifies that intent launches in the background are
+     * allowed for this navigation, and that the intent is then launched if the embedder calls back
+     * that the user has consented.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(93)
+    public void
+    testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogLaunchedWhenBackgroundLaunchesAllowedAndUserConsents()
+            throws Throwable {
+        CallbackHelper onTabRemovedCallbackHelper = new CallbackHelper();
+        TabListCallback tabListCallback = new TabListCallback() {
+            @Override
+            public void onTabRemoved(Tab tab) {
+                onTabRemovedCallbackHelper.notifyCalled();
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        final ExternalIntentInIncognitoCallbackTestImpl externalIntentCallback =
+                new ExternalIntentInIncognitoCallbackTestImpl();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        Assert.assertEquals(true, browser.getProfile().isIncognito());
+                        activity.setIntentInterceptor(intentInterceptor);
+                        browser.registerTabListCallback(tabListCallback);
+
+                        browser.getActiveTab().setExternalIntentInIncognitoCallback(
+                                externalIntentCallback);
+
+                        NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+                        navigateParamsBuilder.allowIntentLaunchesInBackground();
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
+                    }
+                });
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShell(extras);
+
+        // The embedder should be invoked to present the warning dialog rather than WebLayer
+        // presenting the default warning dialog.
+        externalIntentCallback.waitForNotificationOnExternalIntentLaunch();
+        onView(withText(android.R.id.button1)).check(doesNotExist());
+
+        // Have the embedder notify the implementation that the user has consented.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            externalIntentCallback.getOnUserDecisionCallback().onResult(
+                    new Integer(ExternalIntentInIncognitoUserDecision.ALLOW));
+        });
+
+        // The intent should be launched...
+        intentInterceptor.waitForIntent();
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        // ...the tab created for the initial navigation should be closed...
+        onTabRemovedCallbackHelper.waitForFirst();
+
+        // ...and there should now be no tabs in the browser.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(0, numTabs);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.unregisterTabListCallback(tabListCallback); });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent in browser startup in incognito mode
+     * with the embedder having set an ExternalIntentInIncognitoCallback instance causes that
+     * instance to be notified if the embedder specifies that intent launches in the background are
+     * allowed for this navigation, and that the intent is then blocked if the embedder calls back
+     * that the user has not consented.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(93)
+    public void
+    testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogBlockedWhenBackgroundLaunchesAllowedAndUserForbids()
+            throws Throwable {
+        CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                // There should be no additional navigations after the initial one.
+                Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING,
+                        navigation.getUri().toString());
+            }
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(
+                            INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING)) {
+                    onNavigationToIntentFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        final IntentInterceptor intentInterceptor = new IntentInterceptor();
+        final ExternalIntentInIncognitoCallbackTestImpl externalIntentCallback =
+                new ExternalIntentInIncognitoCallbackTestImpl();
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        activity.setIntentInterceptor(intentInterceptor);
+                        Assert.assertEquals(true, browser.getProfile().isIncognito());
+                        browser.getActiveTab().setExternalIntentInIncognitoCallback(
+                                externalIntentCallback);
+                        browser.getActiveTab().getNavigationController().registerNavigationCallback(
+                                navigationCallback);
+
+                        NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+                        navigateParamsBuilder.allowIntentLaunchesInBackground();
+                        browser.getActiveTab().getNavigationController().navigate(
+                                Uri.parse(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING),
+                                navigateParamsBuilder.build());
+                    }
+                });
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShell(extras);
+
+        // The embedder should be invoked to present the warning dialog rather than WebLayer
+        // presenting the default warning dialog.
+        externalIntentCallback.waitForNotificationOnExternalIntentLaunch();
+        onView(withText(android.R.id.button1)).check(doesNotExist());
+
+        // Have the embedder notify the implementation that the user has forbidden the launch.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            externalIntentCallback.getOnUserDecisionCallback().onResult(
+                    new Integer(ExternalIntentInIncognitoUserDecision.DENY));
+        });
+
+        // The navigation should fail...
+        onNavigationToIntentFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched.
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // As there was no fallback Url, there should be zero navigations in the tab.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(0, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            browser.getActiveTab().getNavigationController().unregisterNavigationCallback(
+                    navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent is launched due to the navigation type
+     * being set as from a link with a user gesture.
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentWithNoRedirectLaunched() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL)); });
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests that a direct navigation to an external intent is blocked if the client calls
+     * Navigation#disableIntentProcessing() from the onNavigationStarted() callback.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(97)
+    public void
+    testExternalIntentWithNoRedirectBlockedIfIntentProcessingDisabledOnNavigationStarted()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
+                    navigation.disableIntentProcessing();
+                }
+            }
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
+                    onNavigationToIntentFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+            tab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL));
+        });
+
+        // The navigation should fail...
+        onNavigationToIntentFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched.
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // As there was no fallback Url, there should be only the initial navigation in the tab.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(1, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a navigation to an external intent after a server redirect is blocked if the
+     * client calls Navigation#disableIntentProcessing() from the onNavigationStarted()
+     * callback.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(97)
+    public void
+    testExternalIntentAfterRedirectBlockedIfIntentProcessingDisabledOnNavigationStarted()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                if (navigation.getUri().toString().equals(mRedirectToIntentToSelfURL)) {
+                    navigation.disableIntentProcessing();
+                }
+            }
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
+                    onNavigationToIntentFailedCallbackHelper.notifyCalled();
+                }
+            }
+        };
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+            tab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
+        });
+
+        // The navigation should fail...
+        onNavigationToIntentFailedCallbackHelper.waitForFirst();
+
+        // ...the intent should not have been launched.
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // As there was no fallback Url, there should be only the initial navigation in the tab.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return browser.getActiveTab().getNavigationController().getNavigationListSize();
+        });
+        Assert.assertEquals(1, numNavigationsInTab);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that external intent-related navigation params are not set on browser navigations.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamsNotSetOnBrowserNavigations() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+
+        navigateAndCheckExternalIntentParams(mTestServerSiteUrl, EXPECT_NAVIGATION_COMPLETION,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // Navigating to an unresolvable intent with a fallback URL should result in a followup
+        // browser navigation to the fallback URL.
+        navigateAndCheckExternalIntentParams(mNonResolvableIntentWithFallbackUrl,
+                mTestServerSiteUrl, EXPECT_NAVIGATION_COMPLETION, DOESNT_RESULT_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#wasIntentLaunched() is correctly set on embedder navigations that
+     * resolve to intents.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamSetOnNavigationsToIntents() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        navigateAndCheckExternalIntentParams(INTENT_TO_SELF_URL, EXPECT_NAVIGATION_FAILURE,
+                RESULTS_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mIntentToSelfWithFallbackUrl,
+                EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mRedirectToIntentToSelfURL, INTENT_TO_SELF_URL,
+                EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+        navigateAndCheckExternalIntentParams(mRedirectToCustomSchemeUrlWithDefaultExternalHandler,
+                CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER, EXPECT_NAVIGATION_FAILURE,
+                RESULTS_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // A navigation that results in an intent that cannot be launched should still fail, but
+        // should not have the wasIntentLaunched() parameter set.
+        navigateAndCheckExternalIntentParams(MALFORMED_INTENT_URL, EXPECT_NAVIGATION_FAILURE,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+
+        // The presence of a fallback URL should not impact the state in the navigation failure
+        // callback for a navigation that results in an unresolvable intent.
+        navigateAndCheckExternalIntentParams(mNonResolvableIntentWithFallbackUrl,
+                EXPECT_NAVIGATION_FAILURE, DOESNT_RESULT_IN_EXTERNAL_INTENT,
+                DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#isUserDecidingIntentLaunch() is correctly set on embedder navigations
+     * that resolve to intents in incognito mode.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testUserDecidingExternalIntentNavigationParamSetOnNavigationsToIntentsInIncognito()
+            throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
+        mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL, extras);
+
+        navigateAndCheckExternalIntentParams(INTENT_TO_SELF_URL, EXPECT_NAVIGATION_FAILURE,
+                DOESNT_RESULT_IN_EXTERNAL_INTENT, RESULTS_IN_USER_DECIDING_EXTERNAL_INTENT);
+    }
+
+    /**
+     * Tests that Navigation#wasIntentLaunched() is correctly set on a navigation to an intent that
+     * is initiated via a link click.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(89)
+    public void testExternalIntentNavigationParamSetOnIntentLaunchViaLinkClick() throws Throwable {
+        // Set up all the prerequisites.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        CallbackHelper navigationFailureCallbackHelper = new CallbackHelper();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationFailed(Navigation navigation) {
+                Assert.assertEquals(INTENT_TO_SELF_URL, navigation.getUri().toString());
+                Assert.assertEquals(true, navigation.wasIntentLaunched());
+                Assert.assertEquals(false, navigation.isUserDecidingIntentLaunch());
+
+                navigationFailureCallbackHelper.notifyCalled();
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().registerNavigationCallback(navigationCallback);
+        });
+
+        // Navigate to a URL that has a link to an intent, click on the link, and verify via the
+        // callback that the navigation to the intent fails with the expected state set.
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE);
+        mActivityTestRule.navigateAndWait(url);
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+        navigationFailureCallbackHelper.waitForFirst();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
+        });
+    }
+
+    /**
+     * Tests that a navigation that redirects to an external intent results in the external intent
+     * being launched.
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentAfterRedirectLaunched() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
+        });
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests that a navigation that redirects to a URL with a special scheme that has a default
+     * external handler results in an external intent being launched.
+     */
+    @Test
+    @SmallTest
+    public void testRedirectToCustomSchemeUrlWithDefaultExternalHandlerLaunchesIntent()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.getNavigationController().navigate(
+                    Uri.parse(mRedirectToCustomSchemeUrlWithDefaultExternalHandler));
+        });
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+
+        Assert.assertEquals(
+                INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_ACTION, intent.getAction());
+        Assert.assertEquals(
+                INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests that clicking on a link that goes to an external intent in the same tab results in the
+     * external intent being launched.
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentInSameTabLaunchedOnLinkClick() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE);
+
+        mActivityTestRule.navigateAndWait(url);
+
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests that clicking on a link that goes to an external intent in a new tab results in
+     * a new tab being opened whose URL is that of the intent and the intent being launched,
+     * followed by the new tab being closed.
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentInNewTabLaunchedOnLinkClick() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_NEW_TAB_FILE);
+
+        mActivityTestRule.navigateAndWait(url);
+
+        // Set up listening for the tab addition and removal that we expect to happen.
+        CallbackHelper onTabAddedCallbackHelper = new CallbackHelper();
+        CallbackHelper onTabRemovedCallbackHelper = new CallbackHelper();
+        TabListCallback tabListCallback = new TabListCallback() {
+            @Override
+            public void onTabAdded(Tab tab) {
+                onTabAddedCallbackHelper.notifyCalled();
+            }
+
+            @Override
+            public void onTabRemoved(Tab tab) {
+                onTabRemovedCallbackHelper.notifyCalled();
+            }
+        };
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.registerTabListCallback(tabListCallback); });
+
+        // Grab the original tab before it changes.
+        Tab originalTab = mActivityTestRule.getActivity().getTab();
+
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        // (1) A new tab should be created...
+        onTabAddedCallbackHelper.waitForFirst();
+
+        // (2) The intent should be launched in that tab...
+        intentInterceptor.waitForIntent();
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+
+        // (3) And finally the new tab should be closed.
+        onTabRemovedCallbackHelper.waitForFirst();
+
+        // Now the original tab should be all that's left in the browser, with the display URL being
+        // the original URL.
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(1, numTabs);
+        Assert.assertEquals(mActivityTestRule.getActivity().getTab(), originalTab);
+        Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { browser.unregisterTabListCallback(tabListCallback); });
+    }
+
+    /**
+     * Tests that a navigation that redirects to an external intent with a fallback URL results in
+     * the external intent being launched.
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentWithFallbackUrlAfterRedirectLaunched() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestServer().getURL(
+                "/server-redirect?" + mIntentToSelfWithFallbackUrl);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests that a navigation that redirects to an external intent that can't be handled results in
+     * a failed navigation.
+     */
+    @Test
+    @SmallTest
+    public void testNonHandledExternalIntentAfterRedirectBlocked() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestServer().getURL(
+                "/server-redirect?" + MALFORMED_INTENT_URL);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        // Note that this navigation will not result in a paint.
+        NavigationWaiter waiter = new NavigationWaiter(
+                MALFORMED_INTENT_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should not have changed.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
+     * Tests that a navigation that redirects to an external intent that can't be handled but has a
+     * fallback URL results in a navigation to the fallback URL.
+     */
+    @Test
+    @SmallTest
+    public void testNonHandledExternalIntentWithFallbackUrlAfterRedirectGoesToFallbackUrl()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestServer().getURL(
+                "/server-redirect?" + mNonResolvableIntentWithFallbackUrl);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        NavigationWaiter waiter = new NavigationWaiter(
+                mTestServerSiteUrl, tab, /*expectFailure=*/false, /*waitForPaint=*/true);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should now be the fallback URL.
+        Assert.assertEquals(mTestServerSiteUrl, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
+     * |url| is a URL that redirects to an unhandleable intent but has a fallback URL that redirects
+     * to a handleable intent.
+     * Tests that a navigation to |url| blocks the handleable intent by policy on chained redirects.
+     */
+    @Test
+    @SmallTest
+    @DisabledTest(message = "crbug.com/1329813")
+    public void
+    testNonHandledExternalIntentWithFallbackUrlThatLaunchesIntentAfterRedirectBlocksFallbackIntent()
+            throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestServer().getURL(
+                "/server-redirect?" + mNonResolvableIntentWithFallbackUrlThatLaunchesIntent);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
+
+        NavigationWaiter waiter = new NavigationWaiter(
+                INTENT_TO_SELF_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should not have changed.
+        Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
+     * Tests that going to a page that loads an intent that can be handled in onload() results in
+     * the external intent being launched due to the navigation being specified as being from a link
+     * with a user gesture (if the navigation were specified as being from user typing the intent
+     * would be blocked due to Chrome's policy on not launching intents from user-typed navigations
+     * without a redirect). Also verifies that WebLayer eliminates the navigation entry that
+     * launched the intent, so that the user is back on the original URL (i.e., the URL before that
+     * of the page that launched the intent in onload().
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testExternalIntentViaOnLoadLaunched() throws Throwable {
+        String initialUrl = ABOUT_BLANK_URL;
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(initialUrl);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        mActivityTestRule.navigateAndWait(url);
+
+        intentInterceptor.waitForIntent();
+
+        // The intent should have been launched, and the user should now be back on the original
+        // URL.
+        Assert.assertEquals(initialUrl, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Tests the following flow:
+     * - The user clicks on a link
+     * - This link goes to a page that loads a handleable intent in onload()
+     * This flow should result in (a) the external intent being launched rather than blocked,
+     * because the initial navigation to the page did not occur via user typing, and (b) WebLayer
+     * eliminating the navigation entry that launched the intent, so that the user is back on the
+     * original URL (i.e., the URL before they clicked the link).
+     */
+    @Test
+    @SmallTest
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "https://crbug.com/1176658")
+    public void testUserClicksLinkToPageWithExternalIntentLaunchedViaOnLoad() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url =
+                mActivityTestRule.getTestDataURL(LINK_TO_PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE);
+
+        mActivityTestRule.navigateAndWait(url);
+
+        // Clicking on the link on this page should result in a navigation to the page that loads an
+        // intent in onLoad(), followed by a launching of that intent.
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        String finalUrl =
+                mActivityTestRule.getTestDataURL(PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE);
+        NavigationWaiter waiter =
+                new NavigationWaiter(finalUrl, tab, /*expectFailure=*/false, /*waitForPaint=*/true);
+
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        waiter.waitForNavigation();
+
+        intentInterceptor.waitForIntent();
+
+        // The intent should have been launched, and the user should now be back on the original
+        // URL.
+        Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
+    }
+
+    /**
+     * Verifies that disableIntentProcessing() does in fact disable intent processing.
+     */
+    @Test
+    @SmallTest
+    public void testDisableIntentProcessing() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE);
+
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
+            navigateParamsBuilder.disableIntentProcessing();
+            tab.getNavigationController().navigate(Uri.parse(url), navigateParamsBuilder.build());
+        });
+
+        NavigationWaiter waiter = new NavigationWaiter(
+                INTENT_TO_SELF_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should not have changed.
+        Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
+     * Verifies that for an intent with multiple matching apps that weblayer can handle, we avoid
+     * the disambiguation dialog and stay in weblayer.
+     */
+    @Test
+    @SmallTest
+    public void testAvoidDisambiguationDialog() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        // The data URL isn't valid and will fail the navigation.
+        mActivityTestRule.navigateAndWaitForFailure(SPECIALIZED_DATA_URL);
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+        Assert.assertEquals(SPECIALIZED_DATA_URL, mActivityTestRule.getCurrentDisplayUrl());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FaviconFetcherTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FaviconFetcherTest.java
new file mode 100644
index 0000000..8e2aa40
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FaviconFetcherTest.java
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.FaviconCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/** Tests for FaviconFetcher. */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class FaviconFetcherTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    public void testFaviconFetcher() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().createFaviconFetcher(new FaviconCallback() {
+                @Override
+                public void onFaviconChanged(Bitmap bitmap) {
+                    if (bitmap != null) {
+                        Assert.assertTrue(bitmap.getWidth() > 0);
+                        Assert.assertTrue(bitmap.getHeight() > 0);
+                        callbackHelper.notifyCalled();
+                    }
+                }
+            });
+        });
+        String url = mActivityTestRule.getTestDataURL("simple_page_with_favicon.html");
+        mActivityTestRule.navigateAndWait(url);
+        callbackHelper.waitForFirst();
+
+        // Verify the favicon can get obtained from the Profile.
+        final CallbackHelper downloadCallbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().getProfile().getCachedFaviconForPageUri(
+                    Uri.parse(url), (Bitmap bitmap) -> {
+                        Assert.assertTrue(bitmap != null);
+                        Assert.assertTrue(bitmap.getWidth() > 0);
+                        Assert.assertTrue(bitmap.getHeight() > 0);
+                        downloadCallbackHelper.notifyCalled();
+                    });
+        });
+        downloadCallbackHelper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java
new file mode 100644
index 0000000..817942c0
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java
@@ -0,0 +1,162 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.FindInPageCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests the behavior of FindInPageController and FindInPageCallback.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class FindInPageTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private CallbackImpl mCallback;
+
+    private static class CallbackImpl extends FindInPageCallback {
+        public int mNumberOfMatches;
+        public int mActiveMatchIndex;
+        public BoundedCountDownLatch mResultCountDown;
+        public BoundedCountDownLatch mEndedCountDown;
+
+        @Override
+        public void onFindResult(int numberOfMatches, int activeMatchIndex, boolean finalUpdate) {
+            mNumberOfMatches = numberOfMatches;
+            mActiveMatchIndex = activeMatchIndex;
+            if (finalUpdate) mResultCountDown.countDown();
+        }
+
+        @Override
+        public void onFindEnded() {
+            if (mEndedCountDown != null) mEndedCountDown.countDown();
+        }
+    }
+
+    private void searchFor(String text, boolean forward) {
+        mCallback.mResultCountDown = new BoundedCountDownLatch(1);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getFindInPageController().find(text, forward); });
+        mCallback.mResultCountDown.timedAwait();
+    }
+
+    private void searchFor(String text) {
+        searchFor(text, true);
+    }
+
+    private void verifyFindSessionInactive() {
+        Tab tab = mActivity.getTab();
+        // This verification only works on the active tab; if inactive setFindInPageCallback always
+        // fails.
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return tab == tab.getBrowser().getActiveTab(); }));
+
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            // This call will return false if there's already a find session active.
+            boolean settingCallbackWorked =
+                    tab.getFindInPageController().setFindInPageCallback(new CallbackImpl());
+            // Remove the new callback.
+            tab.getFindInPageController().setFindInPageCallback(null);
+            return settingCallbackWorked;
+        }));
+    }
+
+    public void setUp(String testPage) {
+        String url = mActivityTestRule.getTestDataURL(testPage);
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        mCallback = new CallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().getFindInPageController().setFindInPageCallback(mCallback);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testBasic() {
+        setUp("shakespeare.html");
+
+        // Search.
+        searchFor("de");
+        Assert.assertEquals(mCallback.mNumberOfMatches, 5);
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 0);
+
+        // Search again to activate the next match.
+        searchFor("de");
+        Assert.assertEquals(mCallback.mNumberOfMatches, 5);
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 1);
+
+        // Search backwards to activate the previous match.
+        searchFor("de", false);
+        Assert.assertEquals(mCallback.mNumberOfMatches, 5);
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 0);
+
+        // Add a character to narrow the search.
+        searchFor("des");
+        Assert.assertEquals(mCallback.mNumberOfMatches, 2);
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 0);
+        searchFor("des");
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 1);
+
+        // Simulate a backspace; the active match shouldn't change (although the number of results
+        // and therefore indexing does change).
+        searchFor("de");
+        Assert.assertEquals(mCallback.mNumberOfMatches, 5);
+        Assert.assertEquals(mCallback.mActiveMatchIndex, 4);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().getFindInPageController().setFindInPageCallback(null);
+        });
+        verifyFindSessionInactive();
+    }
+
+    @Test
+    @SmallTest
+    public void testHideOnNavigate() {
+        setUp("shakespeare.html");
+
+        mCallback.mEndedCountDown = new BoundedCountDownLatch(1);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().getNavigationController().navigate(Uri.parse("simple_page.html"));
+        });
+
+        mCallback.mEndedCountDown.timedAwait();
+        verifyFindSessionInactive();
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1248187")
+    public void testHideOnNewTab() {
+        setUp("new_browser.html");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().getActiveTab().setNewTabCallback(new NewTabCallbackImpl());
+        });
+
+        mCallback.mEndedCountDown = new BoundedCountDownLatch(1);
+
+        // This touch creates a new tab.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+
+        mCallback.mEndedCountDown.timedAwait();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java
new file mode 100644
index 0000000..4e5f9cb
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java
@@ -0,0 +1,95 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.RemoteException;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.FullscreenCallback;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Fullscreen test assertions that can only be done using private API.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class FullscreenCallbackPrivateTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private TestFullscreenCallback mDelegate;
+
+    @Before
+    public void setUp() {
+        String url = mActivityTestRule.getTestDataURL("fullscreen.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        mDelegate = new TestFullscreenCallback(mActivityTestRule);
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(
+                mActivityTestRule.getActivity().getApplicationContext());
+    }
+
+    @Test
+    @SmallTest
+    public void testToastNotShownWhenFullscreenRequestIgnored() throws Throwable {
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return getTestWebLayer().didShowFullscreenToast(mActivity.getTab()); }));
+
+        // Touch to enter fullscreen
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForFullscreen();
+        Assert.assertEquals(1, mDelegate.mEnterFullscreenCount);
+
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return getTestWebLayer().didShowFullscreenToast(mActivity.getTab()); }));
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1293419")
+    public void testToastShown() throws Throwable {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().setFullscreenCallback(new FullscreenCallback() {
+                @Override
+                public void onEnterFullscreen(Runnable exitFullscreenRunner) {
+                    mActivity.getWindow().getDecorView().setSystemUiVisibility(
+                            View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+                }
+
+                @Override
+                public void onExitFullscreen() {}
+            });
+        });
+
+        // Touch to enter fullscreen
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        CriteriaHelper.pollUiThread(() -> {
+            try {
+                Criteria.checkThat(getTestWebLayer().didShowFullscreenToast(mActivity.getTab()),
+                        Matchers.is(true));
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackTest.java
new file mode 100644
index 0000000..4179eae7
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenCallbackTest.java
@@ -0,0 +1,145 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisableIf;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.BrowserControlsOffsetCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that FullscreenCallback methods are invoked as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class FullscreenCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private TestFullscreenCallback mDelegate;
+
+    // Launch WL and triggers html fullscreen.
+    private void enterFullscreen() {
+        String url = mActivityTestRule.getTestDataURL("fullscreen.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        mDelegate = new TestFullscreenCallback(mActivityTestRule);
+
+        // First touch enters fullscreen.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForFullscreen();
+        Assert.assertEquals(1, mDelegate.mEnterFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testFullscreen() {
+        enterFullscreen();
+        // Second touch exits.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testExitFullscreenWhenDelegateCleared() {
+        enterFullscreen();
+        // Clearing the FullscreenCallback should exit fullscreen.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().setFullscreenCallback(null); });
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testExitFullscreenUsingRunnable() {
+        enterFullscreen();
+        // Running the runnable supplied to the delegate should exit fullscreen.
+        TestThreadUtils.runOnUiThreadBlocking(mDelegate.mExitFullscreenRunnable);
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testExitFullscreenWhenTabDestroyed() {
+        enterFullscreen();
+        // Destroying the tab should exit fullscreen.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getBrowser().destroyTab(mActivity.getTab()); });
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    /**
+     * Verifies there are no crashes when destroying the fragment in fullscreen.
+     */
+    @Test
+    @SmallTest
+    public void testDestroyFragmentWhileFullscreen() {
+        enterFullscreen();
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mActivity.destroyFragment(); });
+    }
+
+    // Waits for the top offset to go to -height. This means the view is completely hidden.
+    private final class BrowserControlsOffsetCallbackImpl extends BrowserControlsOffsetCallback {
+        private final CallbackHelper mCallbackHelper;
+        BrowserControlsOffsetCallbackImpl(CallbackHelper callbackHelper) {
+            mCallbackHelper = callbackHelper;
+        }
+        @Override
+        public void onTopViewOffsetChanged(int offset) {
+            int height = mActivity.getTopContentsContainer().getHeight();
+            if (height != 0 && offset == -height) {
+                mCallbackHelper.notifyCalled();
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    @DisableIf.
+    Build(sdk_is_less_than = Build.VERSION_CODES.M, message = "https://crbug.com/1159781")
+    public void testTopViewRemainsHiddenOnFullscreenRotation() throws Exception {
+        String url = mActivityTestRule.getTestDataURL("rotation2.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        // Ensure the fragment is not recreated as otherwise things bounce around more.
+        mActivityTestRule.setRetainInstance(true);
+        Assert.assertNotNull(mActivity);
+        mDelegate = new TestFullscreenCallback(mActivityTestRule);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        // The offsets may move around during rotation. Wait for reattachment before installing
+        // the BrowserControlsOffsetCallback.
+        InstrumentationActivity.registerOnCreatedCallback(
+                new InstrumentationActivity.OnCreatedCallback() {
+                    @Override
+                    public void onCreated(Browser browser, InstrumentationActivity activity) {
+                        browser.registerBrowserControlsOffsetCallback(
+                                new BrowserControlsOffsetCallbackImpl(callbackHelper));
+                    }
+                });
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForFullscreen();
+        Assert.assertEquals(1, mDelegate.mEnterFullscreenCount);
+
+        // Rotation should trigger the view being totally hidden.
+        callbackHelper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenSizeTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenSizeTest.java
new file mode 100644
index 0000000..7ca2e88
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FullscreenSizeTest.java
@@ -0,0 +1,120 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that the Web Contents is sized appropriately when in fullscreen and the onscreen keyboard
+ * is shown.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class FullscreenSizeTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1239002")
+    public void testOsk() {
+        // For this test to function, it *cannot* use {@link TestFullscreenCallback}, as that
+        // overrides the fullscreen handling in {@link InstrumentationActivity}.
+        String url = mActivityTestRule.getTestDataURL("fullscreen_with_input.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+
+        int initialHeight = getVisiblePageHeight();
+
+        // First, try without fullscreen. Android should automatically resize the activity window.
+        // First touch focuses input and brings up OSK (if it's enabled).
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        try {
+            CriteriaHelper.pollInstrumentationThread(() -> {
+                Criteria.checkThat(getVisiblePageHeight(), Matchers.lessThan(initialHeight));
+            });
+        } catch (AssertionError e) {
+            // No soft keyboard found. This is possible when a hardware keyboard is attached.
+            // Abort test.
+            Assert.assertNotEquals(mActivity.getResources().getConfiguration().keyboard,
+                    Configuration.KEYBOARD_NOKEYS);
+            Assert.assertNotEquals(mActivity.getResources().getConfiguration().keyboard,
+                    Configuration.KEYBOARD_UNDEFINED);
+            return;
+        }
+
+        // Reset the OSK state.
+        int withKeyboardHeight = getVisiblePageHeight();
+        dismissOsk();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.greaterThan(withKeyboardHeight));
+        });
+
+        // Now, try with fullscreen.
+        // Second touch enters fullscreen.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(
+                    mActivityTestRule.executeScriptAndExtractBoolean("document.webkitIsFullScreen"),
+                    Matchers.is(true));
+        });
+
+        int fsWidth = getVisiblePageWidth();
+        int fsHeight = getVisiblePageHeight();
+
+        // Third touch focuses the input box, bringing up the on-screen keyboard.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        CriteriaHelper.pollInstrumentationThread(
+                () -> { Criteria.checkThat(getVisiblePageHeight(), Matchers.lessThan(fsHeight)); });
+
+        int fsWithOskWidth = getVisiblePageWidth();
+        int fsWithOskHeight = getVisiblePageHeight();
+
+        Assert.assertEquals(fsWithOskWidth, fsWidth);
+        Assert.assertThat(fsWithOskHeight, Matchers.lessThan(fsHeight));
+
+        dismissOsk();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getVisiblePageHeight(), Matchers.greaterThan(fsWithOskHeight));
+        });
+
+        Assert.assertEquals(getVisiblePageWidth(), fsWidth);
+        Assert.assertEquals(getVisiblePageHeight(), fsHeight);
+    }
+
+    private int getVisiblePageWidth() {
+        return mActivityTestRule.executeScriptAndExtractInt("window.innerWidth");
+    }
+
+    private int getVisiblePageHeight() {
+        return mActivityTestRule.executeScriptAndExtractInt("window.innerHeight");
+    }
+
+    private void dismissOsk() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            InputMethodManager inputManager =
+                    (InputMethodManager) mActivity.getSystemService(Activity.INPUT_METHOD_SERVICE);
+            inputManager.hideSoftInputFromWindow(mActivity.getCurrentFocus().getWindowToken(), 0);
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GeolocationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GeolocationTest.java
new file mode 100644
index 0000000..11b0938
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GeolocationTest.java
@@ -0,0 +1,282 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.test.filters.MediumTest;
+
+import org.hamcrest.Matchers;
+import org.json.JSONException;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.ApplicationContextWrapper;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.CriteriaNotSatisfiedException;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that Geolocation Web API works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class GeolocationTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private TestWebLayer mTestWebLayer;
+    private TestWebServer mTestServer;
+    private int mLocationPermission = PackageManager.PERMISSION_GRANTED;
+
+    private static final String RAW_JAVASCRIPT = "var positionCount = 0;"
+            + "var errorCount = 0;"
+            + "function gotPos(position) {"
+            + "  positionCount++;"
+            + "}"
+            + "function errorCallback(error){"
+            + "  errorCount++;"
+            + "}"
+            + "function initiate_getCurrentPosition() {"
+            + "  navigator.geolocation.getCurrentPosition("
+            + "      gotPos, errorCallback, {});"
+            + "}"
+            + "function initiate_watchPosition() {"
+            + "  navigator.geolocation.watchPosition("
+            + "      gotPos, errorCallback, {});"
+            + "}";
+
+    @RequiresApi(Build.VERSION_CODES.M)
+    private class PermissionCompatDelegate implements ActivityCompat.PermissionCompatDelegate {
+        private CallbackHelper mCallbackHelper = new CallbackHelper();
+
+        @Override
+        public boolean requestPermissions(
+                Activity activity, String[] permissions, int requestCode) {
+            mCallbackHelper.notifyCalled();
+            return false;
+        }
+
+        @Override
+        public boolean onActivityResult(
+                Activity activity, int requestCode, int resultCode, Intent data) {
+            return false;
+        }
+
+        public void waitForPermissionsRequest() throws Exception {
+            mCallbackHelper.waitForFirst();
+        }
+    }
+
+    @Before
+    public void setUp() throws Throwable {
+        Context appContext = new ApplicationContextWrapper(ContextUtils.getApplicationContext()) {
+            @Override
+            public int checkPermission(String permission, int pid, int uid) {
+                if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)
+                        || permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION)) {
+                    return mLocationPermission;
+                }
+                return super.checkPermission(permission, pid, uid);
+            }
+        };
+        ContextUtils.initApplicationContextForTests(appContext);
+
+        Bundle extras = new Bundle();
+        // We need to override the context with which to create WebLayer.
+        extras.putBoolean(InstrumentationActivity.EXTRA_CREATE_WEBLAYER, false);
+        mActivity = mActivityTestRule.launchShell(extras);
+        Assert.assertNotNull(mActivity);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivity.loadWebLayerSync(appContext));
+        mActivityTestRule.navigateAndWait("about:blank");
+
+        mTestWebLayer = TestWebLayer.getTestWebLayer(appContext);
+        mTestWebLayer.setSystemLocationSettingEnabled(true);
+        mTestWebLayer.setMockLocationProvider(true /* enable */);
+
+        mTestServer = TestWebServer.start();
+
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("geolocation.html"));
+        ensureGeolocationIsRunning(false);
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        mTestWebLayer.setMockLocationProvider(false /* enable */);
+        ensureGeolocationIsRunning(false);
+    }
+
+    /**
+     * Test for navigator.getCurrentPosition.
+     */
+    @Test
+    @MediumTest
+    public void testGeolocation_getPosition() throws Throwable {
+        mActivityTestRule.executeScriptSync("initiate_getCurrentPosition();", false);
+        waitForDialog();
+        mTestWebLayer.clickPermissionDialogButton(true);
+        waitForCountEqual("positionCount", 1);
+        mActivityTestRule.executeScriptSync("initiate_getCurrentPosition();", false);
+        waitForCountEqual("positionCount", 2);
+        Assert.assertEquals(0, getCountFromJS("errorCount"));
+    }
+
+    /**
+     * Test for navigator.watchPosition, should receive more than one position.
+     */
+    @Test
+    @MediumTest
+    public void testGeolocation_watchPosition() throws Throwable {
+        mActivityTestRule.executeScriptSync("initiate_watchPosition();", false);
+        waitForDialog();
+        mTestWebLayer.clickPermissionDialogButton(true);
+        waitForCountGreaterThan("positionCount", 1);
+        ensureGeolocationIsRunning(true);
+        Assert.assertEquals(0, getCountFromJS("errorCount"));
+    }
+
+    /**
+     * Test that destroying a tab stops geolocation provider.
+     */
+    @Test
+    @MediumTest
+    public void testGeolocation_destroyTabStopsGeolocation() throws Throwable {
+        mActivityTestRule.executeScriptSync("initiate_watchPosition();", false);
+        waitForDialog();
+        mTestWebLayer.clickPermissionDialogButton(true);
+        ensureGeolocationIsRunning(true);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            browser.destroyTab(mActivity.getBrowser().getActiveTab());
+            Assert.assertEquals(0, browser.getTabs().size());
+        });
+        ensureGeolocationIsRunning(false);
+    }
+
+    /**
+     * Test geolocation denied on insecure origins (e.g. javascript).
+     */
+    @Test
+    @MediumTest
+    public void testGeolocation_denyOnInsecureOrigins() throws Throwable {
+        mActivityTestRule.navigateAndWait("about:blank");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().getNavigationController().navigate(
+                    Uri.parse("javascript:" + RAW_JAVASCRIPT + "initiate_getCurrentPosition();"));
+        });
+        waitForCountEqual("errorCount", 1);
+        Assert.assertEquals(0, getCountFromJS("positionCount"));
+    }
+
+    @Test
+    @MediumTest
+    public void testGeolocation_denyFromPrompt() throws Throwable {
+        mActivityTestRule.executeScriptSync("initiate_watchPosition();", false);
+        waitForDialog();
+        mTestWebLayer.clickPermissionDialogButton(false);
+        waitForCountEqual("errorCount", 1);
+        Assert.assertEquals(0, getCountFromJS("positionCount"));
+    }
+
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    public void testRequestSystemPermission() throws Throwable {
+        mActivityTestRule.executeScriptSync("initiate_watchPosition();", false);
+        waitForDialog();
+        mTestWebLayer.clickPermissionDialogButton(true);
+
+        // Reload and deny the system permission, so it is prompted on the next call to geolocation.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("geolocation.html"));
+
+        PermissionCompatDelegate delegate = new PermissionCompatDelegate();
+        ActivityCompat.setPermissionCompatDelegate(delegate);
+        mLocationPermission = PackageManager.PERMISSION_DENIED;
+        mActivityTestRule.executeScriptSync("initiate_watchPosition();", false);
+
+        delegate.waitForPermissionsRequest();
+    }
+
+    // helper methods
+
+    private void waitForCountEqual(String variableName, int count) {
+        CriteriaHelper.pollInstrumentationThread(
+                () -> Criteria.checkThat(getCountFromJS(variableName), Matchers.is(count)));
+    }
+
+    private void waitForDialog() throws Exception {
+        // Make sure the current permission state is "prompt" before waiting for the dialog.
+        mActivityTestRule.executeScriptSync("var queryResult;"
+                        + "navigator.permissions.query({name: 'geolocation'}).then("
+                        + "function(result) { queryResult = result.state; })",
+                false);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            try {
+                String result =
+                        mActivityTestRule.executeScriptSync("queryResult || ''", false)
+                                .getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY);
+                Criteria.checkThat(result, Matchers.not(""));
+            } catch (JSONException ex) {
+                throw new CriteriaNotSatisfiedException(ex);
+            }
+        });
+        Assert.assertEquals("prompt",
+                mActivityTestRule.executeScriptSync("queryResult", false)
+                        .getString(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY));
+        CriteriaHelper.pollInstrumentationThread(
+                () -> { return mTestWebLayer.isPermissionDialogShown(); });
+    }
+
+    private void waitForCountGreaterThan(String variableName, int count) {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getCountFromJS(variableName), Matchers.greaterThan(count));
+        });
+    }
+
+    private void ensureGeolocationIsRunning(boolean running) {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            try {
+                Criteria.checkThat(
+                        mTestWebLayer.isMockLocationProviderRunning(), Matchers.is(running));
+            } catch (RemoteException ex) {
+                throw new CriteriaNotSatisfiedException(ex);
+            }
+        });
+    }
+
+    private int getCountFromJS(String variableName) {
+        int result = -1;
+        try {
+            result = mActivityTestRule.executeScriptSync(variableName, false)
+                             .getInt(InstrumentationActivityTestRule.SCRIPT_RESULT_KEY);
+        } catch (Exception e) {
+            Assert.fail("Unable to get " + variableName);
+        }
+        return result;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountAccessTokenFetcherTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountAccessTokenFetcherTest.java
new file mode 100644
index 0000000..1142c348
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountAccessTokenFetcherTest.java
@@ -0,0 +1,257 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Callback;
+import org.chromium.weblayer.GoogleAccountAccessTokenFetcher;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests to ensure that access token fetching in WebLayer works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class GoogleAccountAccessTokenFetcherTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+    }
+
+    @Test
+    @SmallTest
+    // Ensures the viability of setting the fetcher to null.
+    public void testSetFetcherToNull() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity()
+                    .getBrowser()
+                    .getProfile()
+                    .setGoogleAccountAccessTokenFetcher(null);
+        });
+    }
+
+    @Test
+    @SmallTest
+    // Tests that fetching access tokens from within WebLayer returns the value from the embedder.
+    public void testFetchAccessToken() throws Exception {
+        Profile profile = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser().getProfile(); });
+
+        final Set<String> expectedScopes = new HashSet<String>(Arrays.asList("scope1", "scope2"));
+        final String expectedToken = "accessToken1";
+
+        GoogleAccountAccessTokenFetcherEmbedderImpl fetcherImpl =
+                new GoogleAccountAccessTokenFetcherEmbedderImpl();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { profile.setGoogleAccountAccessTokenFetcher(fetcherImpl); });
+
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        final Callback<String> onTokenFetchedCallback = new Callback<String>() {
+            @Override
+            public void onResult(String value) {
+                Assert.assertEquals(expectedToken, value);
+                callbackHelper.notifyCalled();
+            }
+        };
+
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+        testWebLayer.fetchAccessToken(profile, expectedScopes, onTokenFetchedCallback);
+        Assert.assertEquals(1, fetcherImpl.getNumOutstandingRequests());
+        Assert.assertEquals(expectedScopes, fetcherImpl.getMostRecentRequestScopes());
+
+        fetcherImpl.respondWithTokenForRequest(fetcherImpl.getMostRecentRequestId(), expectedToken);
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    // Tests handling of multiple ongoing requests.
+    public void testMultipleAccessTokenRequests() throws Exception {
+        Profile profile = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser().getProfile(); });
+
+        GoogleAccountAccessTokenFetcherEmbedderImpl fetcherImpl =
+                new GoogleAccountAccessTokenFetcherEmbedderImpl();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { profile.setGoogleAccountAccessTokenFetcher(fetcherImpl); });
+
+        final String expectedToken1 = "accessToken1";
+        final String expectedToken2 = "accessToken2";
+        final String expectedToken3 = "accessToken3";
+
+        final Set<String> expectedScopes1 = new HashSet<String>(Arrays.asList("scope1", "scope2"));
+        final Set<String> expectedScopes2 = new HashSet<String>(Arrays.asList("scope2", "scope3"));
+        final Set<String> expectedScopes3 = new HashSet<String>(Arrays.asList("scope4", "scope5"));
+
+        final String[] accessTokens = {"", "", ""};
+
+        final CallbackHelper callbackHelper1 = new CallbackHelper();
+        final Callback<String> onTokenFetchedCallback1 = new Callback<String>() {
+            @Override
+            public void onResult(String value) {
+                accessTokens[0] = value;
+                callbackHelper1.notifyCalled();
+            }
+        };
+
+        final CallbackHelper callbackHelper2 = new CallbackHelper();
+        final Callback<String> onTokenFetchedCallback2 = new Callback<String>() {
+            @Override
+            public void onResult(String value) {
+                accessTokens[1] = value;
+                callbackHelper2.notifyCalled();
+            }
+        };
+
+        final CallbackHelper callbackHelper3 = new CallbackHelper();
+        final Callback<String> onTokenFetchedCallback3 = new Callback<String>() {
+            @Override
+            public void onResult(String value) {
+                accessTokens[2] = value;
+                callbackHelper3.notifyCalled();
+            }
+        };
+
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+
+        // Make the first two access token requests.
+        testWebLayer.fetchAccessToken(profile, expectedScopes1, onTokenFetchedCallback1);
+        Assert.assertEquals(1, fetcherImpl.getNumOutstandingRequests());
+        Assert.assertEquals(expectedScopes1, fetcherImpl.getMostRecentRequestScopes());
+        int requestId1 = fetcherImpl.getMostRecentRequestId();
+
+        testWebLayer.fetchAccessToken(profile, expectedScopes2, onTokenFetchedCallback2);
+        Assert.assertEquals(2, fetcherImpl.getNumOutstandingRequests());
+        Assert.assertEquals(expectedScopes2, fetcherImpl.getMostRecentRequestScopes());
+        int requestId2 = fetcherImpl.getMostRecentRequestId();
+
+        // Resolve the second request.
+        fetcherImpl.respondWithTokenForRequest(requestId2, expectedToken2);
+        callbackHelper2.waitForFirst();
+        Assert.assertEquals("", accessTokens[0]);
+        Assert.assertEquals(expectedToken2, accessTokens[1]);
+        Assert.assertEquals("", accessTokens[2]);
+
+        // Make the third request.
+        testWebLayer.fetchAccessToken(profile, expectedScopes3, onTokenFetchedCallback3);
+        Assert.assertEquals(2, fetcherImpl.getNumOutstandingRequests());
+        Assert.assertEquals(expectedScopes3, fetcherImpl.getMostRecentRequestScopes());
+        int requestId3 = fetcherImpl.getMostRecentRequestId();
+
+        // Resolve the first request.
+        fetcherImpl.respondWithTokenForRequest(requestId1, expectedToken1);
+        callbackHelper1.waitForFirst();
+        Assert.assertEquals(expectedToken1, accessTokens[0]);
+        Assert.assertEquals(expectedToken2, accessTokens[1]);
+        Assert.assertEquals("", accessTokens[2]);
+
+        // Resolve the third request.
+        fetcherImpl.respondWithTokenForRequest(requestId3, expectedToken3);
+        callbackHelper2.waitForFirst();
+        Assert.assertEquals(expectedToken1, accessTokens[0]);
+        Assert.assertEquals(expectedToken2, accessTokens[1]);
+        Assert.assertEquals(expectedToken3, accessTokens[2]);
+    }
+
+    @Test
+    @SmallTest
+    // Tests that WebLayer forwards invalid access token notifications to the embedder.
+    public void testOnAccessTokenIdentifiedAsInvalid() throws Exception {
+        Profile profile = TestThreadUtils.runOnUiThreadBlocking(
+                () -> { return mActivityTestRule.getActivity().getBrowser().getProfile(); });
+
+        final Set<String> scopesForInvalidToken =
+                new HashSet<String>(Arrays.asList("scope1", "scope2"));
+        final String invalidToken = "accessToken1";
+
+        GoogleAccountAccessTokenFetcherEmbedderImpl fetcherImpl =
+                new GoogleAccountAccessTokenFetcherEmbedderImpl();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { profile.setGoogleAccountAccessTokenFetcher(fetcherImpl); });
+
+        Assert.assertNull(fetcherImpl.getScopesForMostRecentInvalidToken());
+        Assert.assertNull(fetcherImpl.getMostRecentInvalidToken());
+
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+        testWebLayer.fireOnAccessTokenIdentifiedAsInvalid(
+                profile, scopesForInvalidToken, invalidToken);
+
+        Assert.assertEquals(
+                scopesForInvalidToken, fetcherImpl.getScopesForMostRecentInvalidToken());
+        Assert.assertEquals(invalidToken, fetcherImpl.getMostRecentInvalidToken());
+    }
+
+    private class GoogleAccountAccessTokenFetcherEmbedderImpl
+            extends GoogleAccountAccessTokenFetcher {
+        private HashMap<Integer, Callback<String>> mOutstandingRequests =
+                new HashMap<Integer, Callback<String>>();
+        private int mMostRecentRequestId;
+        private Set<String> mMostRecentRequestScopes;
+        private Set<String> mScopesForMostRecentInvalidToken;
+        private String mMostRecentInvalidToken;
+
+        @Override
+        public void fetchAccessToken(Set<String> scopes, Callback<String> onTokenFetched) {
+            mMostRecentRequestScopes = scopes;
+            mMostRecentRequestId++;
+            mOutstandingRequests.put(mMostRecentRequestId, onTokenFetched);
+        }
+
+        @Override
+        public void onAccessTokenIdentifiedAsInvalid(Set<String> scopes, String token) {
+            mScopesForMostRecentInvalidToken = scopes;
+            mMostRecentInvalidToken = token;
+        }
+
+        int getMostRecentRequestId() {
+            return mMostRecentRequestId;
+        }
+
+        Set<String> getMostRecentRequestScopes() {
+            return mMostRecentRequestScopes;
+        }
+
+        int getNumOutstandingRequests() {
+            return mOutstandingRequests.size();
+        }
+
+        Set<String> getScopesForMostRecentInvalidToken() {
+            return mScopesForMostRecentInvalidToken;
+        }
+
+        String getMostRecentInvalidToken() {
+            return mMostRecentInvalidToken;
+        }
+
+        void respondWithTokenForRequest(int requestId, String token) {
+            Callback<String> callback = mOutstandingRequests.get(requestId);
+            assert callback != null;
+            mOutstandingRequests.remove(requestId);
+
+            callback.onResult(token);
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountsTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountsTest.java
new file mode 100644
index 0000000..94b0691
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/GoogleAccountsTest.java
@@ -0,0 +1,108 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.GoogleAccountServiceType;
+import org.chromium.weblayer.GoogleAccountsCallback;
+import org.chromium.weblayer.GoogleAccountsParams;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+;
+
+/**
+ * Tests for the Google accounts API.
+ */
+@CommandLineFlags.Add({"ignore-certificate-errors"})
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class GoogleAccountsTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivityTestRule.getTestServerRule().setServerUsesHttps(true);
+
+        // We need to add this to the command line directly instead of using @CommandLineFlags.Add
+        // since it uses the test server URL which is not available for the annotatoin.
+        CommandLine.getInstance().appendSwitchWithValue(
+                "gaia-url", mActivityTestRule.getTestServer().getURL("/"));
+        mActivityTestRule.writeCommandLineFile();
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+    }
+
+    @Test
+    @SmallTest
+    public void testBasic() throws Exception {
+        GoogleAccountsCallbackImpl callback = new GoogleAccountsCallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().getActiveTab().setGoogleAccountsCallback(callback);
+        });
+
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("google_accounts.html"));
+        GoogleAccountsParams params = callback.waitForGoogleAccounts();
+        Assert.assertEquals(params.serviceType, GoogleAccountServiceType.ADD_SESSION);
+        Assert.assertEquals(params.email, "foo@bar.com");
+        Assert.assertEquals(params.continueUri.toString(), "https://blah.com");
+        Assert.assertTrue(params.isSameTab);
+    }
+
+    @Test
+    @SmallTest
+    public void testRequestHeader() throws Exception {
+        GoogleAccountsCallbackImpl callback = new GoogleAccountsCallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().getActiveTab().setGoogleAccountsCallback(callback);
+        });
+
+        String url = mActivityTestRule.getTestServer().getURL("/echoheader?X-Chrome-Connected");
+        mActivityTestRule.navigateAndWait(url);
+        Assert.assertEquals(
+                mActivityTestRule.executeScriptAndExtractString("document.body.innerText"),
+                "source=WebLayer,mode=3,enable_account_consistency=true,"
+                        + "consistency_enabled_by_default=false");
+
+        // Remove the callback, the header should no longer be sent.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().setGoogleAccountsCallback(null); });
+        mActivityTestRule.navigateAndWait(url);
+        Assert.assertEquals(
+                mActivityTestRule.executeScriptAndExtractString("document.body.innerText"), "None");
+    }
+
+    private static class GoogleAccountsCallbackImpl extends GoogleAccountsCallback {
+        private CallbackHelper mHelper = new CallbackHelper();
+        private GoogleAccountsParams mParams;
+
+        @Override
+        public void onGoogleAccountsRequest(GoogleAccountsParams params) {
+            mParams = params;
+            mHelper.notifyCalled();
+        }
+
+        @Override
+        public String getGaiaId() {
+            return "";
+        }
+
+        public GoogleAccountsParams waitForGoogleAccounts() throws Exception {
+            mHelper.waitForFirst();
+            return mParams;
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InfoBarTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InfoBarTest.java
new file mode 100644
index 0000000..805023f
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InfoBarTest.java
@@ -0,0 +1,200 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.RemoteException;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+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.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Test for infobars.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@CommandLineFlags.Add("enable-features=ImmediatelyHideBrowserControlsForTest")
+@DisabledTest(message = "crbug.com/1223953, crbug.com/1098625")
+public class InfoBarTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private Tab getActiveTab() {
+        return mActivityTestRule.getActivity().getBrowser().getActiveTab();
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(
+                mActivityTestRule.getActivity().getApplicationContext());
+    }
+
+    private View getInfoBarContainerView() throws Exception {
+        return TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                return getTestWebLayer().getInfoBarContainerView(getActiveTab());
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private void addInfoBarToActiveTab() throws Exception {
+        CallbackHelper helper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                getTestWebLayer().addInfoBar(getActiveTab(), () -> { helper.notifyCalled(); });
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        helper.waitForFirst();
+    }
+
+    private void setAccessibilityEnabled(boolean value) {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                getTestWebLayer().setAccessibilityEnabled(value);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private boolean canInfoBarContainerScroll() throws Exception {
+        return TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                return getTestWebLayer().canInfoBarContainerScroll(getActiveTab());
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    @Before
+    public void setUp() throws Throwable {
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+
+        BrowserControlsHelper.createAndBlockUntilBrowserControlsInitializedInSetUp(activity);
+    }
+
+    @Test
+    @SmallTest
+    /**
+     * Tests that creation of an infobar impacts the state of the infobar container view as
+     * expected.
+     *
+     */
+    public void testAddInfoBar() throws Exception {
+        View infoBarContainerView = getInfoBarContainerView();
+        Assert.assertEquals(infoBarContainerView.getHeight(), 0);
+
+        addInfoBarToActiveTab();
+
+        Assert.assertTrue(infoBarContainerView.getHeight() > 0);
+        Assert.assertEquals(View.VISIBLE, infoBarContainerView.getVisibility());
+    }
+
+    @Test
+    @SmallTest
+    /**
+     * Tests that infobars respond to scrolling.
+     *
+     */
+    public void testScrolling() throws Exception {
+        addInfoBarToActiveTab();
+
+        View infoBarContainerView = getInfoBarContainerView();
+        Assert.assertEquals(0, (int) infoBarContainerView.getTranslationY());
+
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+        int infoBarContainerViewHeight = infoBarContainerView.getHeight();
+        Assert.assertTrue(infoBarContainerViewHeight > 0);
+
+        // Scroll down and check that infobar container view is translated in response.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, -infoBarContainerViewHeight);
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat((int) infoBarContainerView.getTranslationY(),
+                    Matchers.is(infoBarContainerViewHeight));
+        });
+
+        // Scroll back up and check that infobar container view is translated in response.
+        EventUtils.simulateDragFromCenterOfView(
+                activity.getWindow().getDecorView(), 0, infoBarContainerViewHeight);
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat((int) infoBarContainerView.getTranslationY(), Matchers.is(0));
+        });
+    }
+
+    @Test
+    @SmallTest
+    /**
+     * Tests that the infobar container view is removed as part of tab destruction.
+     *
+     */
+    public void testTabDestruction() throws Exception {
+        View infoBarContainerView = getInfoBarContainerView();
+        Assert.assertNotNull(infoBarContainerView.getParent());
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivityTestRule.getActivity().getBrowser();
+            browser.destroyTab(getActiveTab());
+        });
+
+        Assert.assertEquals(infoBarContainerView.getParent(), null);
+    }
+
+    @Test
+    @SmallTest
+    /**
+     * Tests that if the infobar container view is hidden, its visibility is restored on navigation.
+     *
+     */
+    public void testVisibilityRestoredOnNavigation() throws Exception {
+        View infoBarContainerView = getInfoBarContainerView();
+        Assert.assertEquals(infoBarContainerView.getVisibility(), View.VISIBLE);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { infoBarContainerView.setVisibility(View.GONE); });
+        Assert.assertEquals(infoBarContainerView.getVisibility(), View.GONE);
+
+        mActivityTestRule.navigateAndWait("about:blank");
+        Assert.assertEquals(infoBarContainerView.getVisibility(), View.VISIBLE);
+    }
+
+    // Tests that infobar container is blocked from scrolling when accessibility is enabled.
+    @Test
+    @SmallTest
+    public void testAccessibility() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.getActivity();
+
+        // Turn on accessibility, which should disable the infobar container from scrolling. This
+        // polls as setAccessibilityEnabled() is async.
+        setAccessibilityEnabled(true);
+        CriteriaHelper.pollInstrumentationThread(() -> !canInfoBarContainerScroll());
+
+        // Turn accessibility off and verify that the infobar container can scroll.
+        setAccessibilityEnabled(false);
+        CriteriaHelper.pollInstrumentationThread(() -> canInfoBarContainerScroll());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InputTypesTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InputTypesTest.java
new file mode 100644
index 0000000..fb188bae
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InputTypesTest.java
@@ -0,0 +1,349 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.provider.MediaStore;
+
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.ApplicationContextWrapper;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.File;
+import java.util.Arrays;
+
+/**
+ * Tests that file inputs work as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class InputTypesTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private File mTempFile;
+    private File mTestDir;
+    private int mCameraPermission = PackageManager.PERMISSION_GRANTED;
+
+    private class FileIntentInterceptor implements InstrumentationActivity.IntentInterceptor {
+        public Intent mLastIntent;
+
+        private Intent mResponseIntent;
+        private int mResultCode = Activity.RESULT_CANCELED;
+        private CallbackHelper mCallbackHelper = new CallbackHelper();
+
+        @Override
+        public void interceptIntent(Intent intent, int requestCode, Bundle options) {
+            new Handler().post(() -> {
+                mActivityTestRule.getActivity().getActivityResultRegistry().dispatchResult(
+                        requestCode, mResultCode, mResponseIntent);
+                mLastIntent = intent;
+                mCallbackHelper.notifyCalled();
+            });
+        }
+
+        public void waitForIntent() {
+            try {
+                mCallbackHelper.waitForCallback(0);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setResponse(int resultCode, Intent response) {
+            mResponseIntent = response;
+            mResultCode = resultCode;
+        }
+    }
+
+    private FileIntentInterceptor mIntentInterceptor = new FileIntentInterceptor();
+
+    @RequiresApi(Build.VERSION_CODES.M)
+    private class PermissionCompatDelegate implements ActivityCompat.PermissionCompatDelegate {
+        public int mResult = PackageManager.PERMISSION_DENIED;
+        private CallbackHelper mCallbackHelper = new CallbackHelper();
+
+        @Override
+        public boolean requestPermissions(
+                Activity activity, String[] permissions, int requestCode) {
+            new Handler().post(() -> {
+                int[] results = new int[permissions.length];
+                Arrays.fill(results, mResult);
+                mCameraPermission = mResult;
+                activity.onRequestPermissionsResult(requestCode, permissions, results);
+                mCallbackHelper.notifyCalled();
+            });
+            return true;
+        }
+
+        @Override
+        public boolean onActivityResult(
+                Activity activity, int requestCode, int resultCode, Intent data) {
+            return false;
+        }
+
+        public void waitForPermissionsRequest() {
+            try {
+                mCallbackHelper.waitForCallback(0);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setResult(int result) {
+            mResult = result;
+        }
+    }
+
+    private PermissionCompatDelegate mPermissionCompatDelegate = new PermissionCompatDelegate();
+
+    @Before
+    public void setUp() throws Exception {
+        ContextUtils.initApplicationContextForTests(
+                new ApplicationContextWrapper(ContextUtils.getApplicationContext()) {
+                    @Override
+                    public int checkPermission(String permission, int pid, int uid) {
+                        if (permission.equals(Manifest.permission.CAMERA)) {
+                            return mCameraPermission;
+                        }
+                        return super.checkPermission(permission, pid, uid);
+                    }
+                });
+        ActivityCompat.setPermissionCompatDelegate(mPermissionCompatDelegate);
+
+        mTestDir = new File(UrlUtils.getIsolatedTestFilePath("weblayer"));
+        if (!mTestDir.exists()) mTestDir.mkdir();
+        mTempFile = File.createTempFile("file", null, mTestDir);
+        Intent response = new Intent();
+        response.setData(Uri.fromFile(mTempFile));
+        mIntentInterceptor.setResponse(Activity.RESULT_OK, response);
+
+        Bundle extras = new Bundle();
+        // We need to override the context with which to create WebLayer.
+        extras.putBoolean(InstrumentationActivity.EXTRA_CREATE_WEBLAYER, false);
+        InstrumentationActivity activity = mActivityTestRule.launchShell(extras);
+        activity.setIntentInterceptor(mIntentInterceptor);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.loadWebLayerSync(ContextUtils.getApplicationContext()));
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("input_types.html"));
+    }
+
+    @After
+    public void tearDown() {
+        ActivityCompat.setPermissionCompatDelegate(null);
+        if (mTempFile != null) {
+            mTempFile.delete();
+        }
+        if (mTestDir != null) {
+            mTestDir.delete();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputBasic() {
+        String id = "input_file";
+
+        openFileInputWithId(id);
+
+        Assert.assertFalse(getContentIntent().hasCategory(Intent.CATEGORY_OPENABLE));
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    public void testFileInputCameraPermissionGranted() throws Exception {
+        mCameraPermission = PackageManager.PERMISSION_DENIED;
+        mPermissionCompatDelegate.setResult(PackageManager.PERMISSION_GRANTED);
+        String id = "input_file";
+
+        openFileInputWithId(id);
+        mPermissionCompatDelegate.waitForPermissionsRequest();
+
+        Parcelable[] intents = mIntentInterceptor.mLastIntent.getParcelableArrayExtra(
+                Intent.EXTRA_INITIAL_INTENTS);
+        Assert.assertFalse(intents.length == 0);
+        Assert.assertEquals(MediaStore.ACTION_IMAGE_CAPTURE, ((Intent) intents[0]).getAction());
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    public void testFileInputCameraPermissionDenied() throws Exception {
+        mCameraPermission = PackageManager.PERMISSION_DENIED;
+        mPermissionCompatDelegate.setResult(PackageManager.PERMISSION_DENIED);
+        String id = "input_file";
+
+        openFileInputWithId(id);
+        mPermissionCompatDelegate.waitForPermissionsRequest();
+
+        Parcelable[] intents = mIntentInterceptor.mLastIntent.getParcelableArrayExtra(
+                Intent.EXTRA_INITIAL_INTENTS);
+        for (Parcelable intent : intents) {
+            Assert.assertNotEquals(MediaStore.ACTION_IMAGE_CAPTURE, ((Intent) intent).getAction());
+        }
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputCancel() {
+        String id = "input_file";
+
+        // First add a file.
+        openFileInputWithId(id);
+        waitForNumFiles(id, 1);
+
+        // Now cancel the intent.
+        mIntentInterceptor.setResponse(Activity.RESULT_CANCELED, null);
+        openFileInputWithId(id);
+        waitForNumFiles(id, 0);
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputText() {
+        String id = "input_text";
+
+        openFileInputWithId(id);
+
+        Assert.assertTrue(getContentIntent().hasCategory(Intent.CATEGORY_OPENABLE));
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputAny() {
+        String id = "input_any";
+
+        openFileInputWithId(id);
+
+        Assert.assertFalse(getContentIntent().hasCategory(Intent.CATEGORY_OPENABLE));
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputMultiple() throws Exception {
+        Intent response = new Intent();
+        ClipData clipData = ClipData.newUri(mActivityTestRule.getActivity().getContentResolver(),
+                "uris", Uri.fromFile(mTempFile));
+        File otherTempFile = File.createTempFile("file2", null, mTestDir);
+        clipData.addItem(new ClipData.Item(Uri.fromFile(otherTempFile)));
+        response.setClipData(clipData);
+        mIntentInterceptor.setResponse(Activity.RESULT_OK, response);
+        String id = "input_file_multiple";
+
+        openFileInputWithId(id);
+
+        Intent contentIntent = getContentIntent();
+        Assert.assertFalse(contentIntent.hasCategory(Intent.CATEGORY_OPENABLE));
+        Assert.assertTrue(contentIntent.hasExtra(Intent.EXTRA_ALLOW_MULTIPLE));
+
+        waitForNumFiles(id, 2);
+        otherTempFile.delete();
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputImage() {
+        String id = "input_image";
+
+        openFileInputWithId(id);
+
+        Assert.assertEquals(
+                MediaStore.ACTION_IMAGE_CAPTURE, mIntentInterceptor.mLastIntent.getAction());
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testFileInputAudio() {
+        String id = "input_audio";
+
+        openFileInputWithId(id);
+
+        Assert.assertEquals(MediaStore.Audio.Media.RECORD_SOUND_ACTION,
+                mIntentInterceptor.mLastIntent.getAction());
+
+        waitForNumFiles(id, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testColorInput() {
+        // Just make sure we don't crash when opening the color picker.
+        mActivityTestRule.executeScriptSync("var done = false; document.onclick = function() {"
+                        + "document.getElementById('input_color').click(); done = true;}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+        CriteriaHelper.pollInstrumentationThread(
+                () -> { return mActivityTestRule.executeScriptAndExtractBoolean("done"); });
+    }
+
+    private void openFileInputWithId(String id) {
+        // We need to click the input after user input, otherwise it won't open due to security
+        // policy.
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('" + id + "').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+        mIntentInterceptor.waitForIntent();
+    }
+
+    private void waitForNumFiles(String id, int num) {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            int actual = mActivityTestRule.executeScriptAndExtractInt(
+                    "document.getElementById('" + id + "').files.length");
+            Criteria.checkThat(actual, Matchers.is(num));
+        });
+    }
+
+    private Intent getContentIntent() {
+        Assert.assertEquals(Intent.ACTION_CHOOSER, mIntentInterceptor.mLastIntent.getAction());
+        Intent contentIntent =
+                (Intent) mIntentInterceptor.mLastIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+        Assert.assertNotNull(contentIntent);
+        return contentIntent;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java
new file mode 100644
index 0000000..22ee3aba
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java
@@ -0,0 +1,307 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.lifecycle.Stage;
+
+import androidx.fragment.app.Fragment;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.test.util.ApplicationTestUtils;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.EmbeddedTestServerRule;
+import org.chromium.weblayer.CookieManager;
+import org.chromium.weblayer.NavigationController;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.WebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * ActivityTestRule for InstrumentationActivity.
+ *
+ * Test can use this ActivityTestRule to launch or get InstrumentationActivity.
+ */
+public class InstrumentationActivityTestRule
+        extends WebLayerActivityTestRule<InstrumentationActivity> {
+    /** The top level key of the JSON object returned by executeScriptSync(). */
+    public static final String SCRIPT_RESULT_KEY = "result";
+
+    @Rule
+    private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
+
+    private static final class StringCallbackHelper extends CallbackHelper {
+        private String mResult;
+
+        public String getResult() {
+            return mResult;
+        }
+
+        public void notifyCalled(String result) {
+            mResult = result;
+            notifyCalled();
+        }
+    }
+
+    public InstrumentationActivityTestRule() {
+        super(InstrumentationActivity.class);
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return super.apply(mTestServerRule.apply(base, description), description);
+    }
+
+    public WebLayer getWebLayer() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return WebLayer.loadSync(getContextForWebLayer()); });
+    }
+
+    public Context getContextForWebLayer() {
+        return InstrumentationRegistry.getTargetContext().getApplicationContext();
+    }
+
+    /**
+     * Starts the WebLayer activity with the given extras Bundle. This does not create and load
+     * WebLayer.
+     */
+    public InstrumentationActivity launchShell(Bundle extras) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.putExtras(extras);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setComponent(
+                new ComponentName(InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                        InstrumentationActivity.class));
+        launchActivity(intent);
+        return getActivity();
+    }
+
+    /**
+     * Starts the WebLayer activity with the given extras Bundle and completely loads the given URL
+     * (this calls navigateAndWait()).
+     */
+    public InstrumentationActivity launchShellWithUrl(String url, Bundle extras) {
+        InstrumentationActivity activity = launchShell(extras);
+        Assert.assertNotNull(activity);
+        try {
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> activity.loadWebLayerSync(getContextForWebLayer()));
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+        if (url != null) navigateAndWait(url);
+        return activity;
+    }
+
+    /**
+     * Starts the WebLayer activity and completely loads the given URL (this calls
+     * navigateAndWait()).
+     */
+    public InstrumentationActivity launchShellWithUrl(String url) {
+        return launchShellWithUrl(url, new Bundle());
+    }
+
+    /**
+     * Loads the given URL in the shell.
+     */
+    public void navigateAndWait(String url) {
+        navigateAndWait(getActivity().getTab(), url, true /* waitForPaint */);
+    }
+
+    public void navigateAndWait(Tab tab, String url, boolean waitForPaint) {
+        (new NavigationWaiter(url, tab, false /* expectFailure */, waitForPaint)).navigateAndWait();
+    }
+
+    /**
+     * Loads the given URL in the shell, expecting failure.
+     */
+    public void navigateAndWaitForFailure(String url) {
+        navigateAndWaitForFailure(getActivity().getTab(), url, true /* waitForPaint */);
+    }
+
+    public void navigateAndWaitForFailure(Tab tab, String url, boolean waitForPaint) {
+        (new NavigationWaiter(url, tab, true /* expectFailure */, waitForPaint)).navigateAndWait();
+    }
+
+    public void recreateByRotatingToLandscape() {
+        setActivity(ApplicationTestUtils.waitForActivityWithClass(
+                InstrumentationActivity.class, Stage.RESUMED, () -> {
+                    getActivity().setRequestedOrientation(
+                            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                }));
+    }
+
+    /**
+     * Executes the script passed in and waits for the result. Wraps that result in a JSONObject for
+     * convenience of callers that want to process that result as a type other than String.
+     */
+    public JSONObject executeScriptSync(String script, boolean useSeparateIsolate, Tab tab) {
+        StringCallbackHelper callbackHelper = new StringCallbackHelper();
+        int count = callbackHelper.getCallCount();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab scriptTab = tab == null ? getActivity().getBrowser().getActiveTab() : tab;
+            scriptTab.executeScript(script, useSeparateIsolate,
+                    (String result) -> { callbackHelper.notifyCalled(result); });
+        });
+        try {
+            callbackHelper.waitForCallback(count);
+        } catch (TimeoutException e) {
+            throw new RuntimeException(e);
+        }
+        JSONObject resultAsJSONObject;
+        try {
+            resultAsJSONObject = new JSONObject(
+                    "{\"" + SCRIPT_RESULT_KEY + "\":" + callbackHelper.getResult() + "}");
+        } catch (JSONException e) {
+            // This should never happen since the result should be well formed.
+            throw new RuntimeException(e);
+        }
+        return resultAsJSONObject;
+    }
+
+    public JSONObject executeScriptSync(String script, boolean useSeparateIsolate) {
+        return executeScriptSync(script, useSeparateIsolate, null);
+    }
+
+    public int executeScriptAndExtractInt(String script) {
+        try {
+            return executeScriptSync(script, true /* useSeparateIsolate */)
+                    .getInt(SCRIPT_RESULT_KEY);
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public String executeScriptAndExtractString(String script) {
+        return executeScriptAndExtractString(script, true /* useSeparateIsolate */);
+    }
+
+    public String executeScriptAndExtractString(String script, boolean useSeparateIsolate) {
+        try {
+            return executeScriptSync(script, useSeparateIsolate).getString(SCRIPT_RESULT_KEY);
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean executeScriptAndExtractBoolean(String script) {
+        return executeScriptAndExtractBoolean(script, true /* useSeparateIsolate */);
+    }
+
+    public boolean executeScriptAndExtractBoolean(String script, boolean useSeparateIsolate) {
+        try {
+            return executeScriptSync(script, useSeparateIsolate).getBoolean(SCRIPT_RESULT_KEY);
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public InstrumentationActivity launchWithProfile(String profileName) {
+        Bundle extras = new Bundle();
+        extras.putString(InstrumentationActivity.EXTRA_PROFILE_NAME, profileName);
+        String url = "data:text,foo";
+        return launchShellWithUrl(url, extras);
+    }
+
+    public EmbeddedTestServer getTestServer() {
+        return mTestServerRule.getServer();
+    }
+
+    public EmbeddedTestServerRule getTestServerRule() {
+        return mTestServerRule;
+    }
+
+    public String getTestDataURL(String path) {
+        return getTestServer().getURL("/weblayer/test/data/" + path);
+    }
+
+    // Returns the URL that is currently being displayed to the user.
+    public String getCurrentDisplayUrl() {
+        InstrumentationActivity activity = getActivity();
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            NavigationController navigationController =
+                    activity.getBrowser().getActiveTab().getNavigationController();
+
+            if (navigationController.getNavigationListSize() == 0) {
+                return null;
+            }
+
+            // TODO(crbug.com/1066382): This will not be correct in the case where the initial
+            // navigation in |tab| was a failed navigation and there have been no more navigations
+            // since then.
+            return navigationController
+                    .getNavigationEntryDisplayUri(
+                            navigationController.getNavigationListCurrentIndex())
+                    .toString();
+        });
+    }
+
+    public void setRetainInstance(boolean retain) {
+        TestThreadUtils.runOnUiThreadBlocking(() -> getActivity().setRetainInstance(retain));
+    }
+
+    public Fragment getFragment() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> getActivity().getFragment());
+    }
+
+    public boolean setCookie(CookieManager cookieManager, Uri uri, String value) throws Exception {
+        Boolean[] resultHolder = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            cookieManager.setCookie(uri, value, (Boolean result) -> {
+                resultHolder[0] = result;
+                callbackHelper.notifyCalled();
+            });
+        });
+        callbackHelper.waitForFirst();
+        return resultHolder[0];
+    }
+
+    public String getCookie(CookieManager cookieManager, Uri uri) throws Exception {
+        String[] resultHolder = new String[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            cookieManager.getCookie(uri, (String result) -> {
+                resultHolder[0] = result;
+                callbackHelper.notifyCalled();
+            });
+        });
+        callbackHelper.waitForFirst();
+        return resultHolder[0];
+    }
+
+    public List<String> getResponseCookies(CookieManager cookieManager, Uri uri) throws Exception {
+        List<String> finalResult = new ArrayList<>();
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            cookieManager.getResponseCookies(uri, (List<String> result) -> {
+                finalResult.addAll(result);
+                callbackHelper.notifyCalled();
+            });
+        });
+        callbackHelper.waitForFirst();
+        return finalResult;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaCaptureTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaCaptureTest.java
new file mode 100644
index 0000000..57133bf1
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaCaptureTest.java
@@ -0,0 +1,266 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.webkit.ValueCallback;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.filters.MediumTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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.DisableIf;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.UiDisableIf;
+import org.chromium.weblayer.MediaCaptureCallback;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that Media Capture and Streams Web API (MediaStream) works as expected.
+ */
+@CommandLineFlags.Add({"ignore-certificate-errors"})
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class MediaCaptureTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private TestWebLayer mTestWebLayer;
+    private CallbackImpl mCaptureCallback;
+
+    private static class CallbackImpl extends MediaCaptureCallback {
+        boolean mAudio;
+        boolean mVideo;
+        public BoundedCountDownLatch mRequestedCountDown;
+        public BoundedCountDownLatch mStateCountDown;
+
+        @Override
+        public void onMediaCaptureRequested(
+                boolean audio, boolean video, ValueCallback<Boolean> requestResult) {
+            requestResult.onReceiveValue(true);
+            mRequestedCountDown.countDown();
+        }
+
+        @Override
+        public void onMediaCaptureStateChanged(boolean audio, boolean video) {
+            mAudio = audio;
+            mVideo = video;
+            mStateCountDown.countDown();
+        }
+    }
+
+    @Before
+    public void setUp() throws Throwable {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        Assert.assertNotNull(mActivity);
+
+        mTestWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+        mActivityTestRule.getTestServerRule().setServerUsesHttps(true);
+
+        mCaptureCallback = new CallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().getMediaCaptureController().setMediaCaptureCallback(
+                    mCaptureCallback);
+        });
+    }
+
+    /**
+     * Basic test for a stream that includes audio and video.
+     */
+    @Test
+    @MediumTest
+    @DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/1107380
+    public void basic() throws Throwable {
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/weblayer/test/data/getusermedia.html"));
+
+        grantPermissionAndWaitForStreamToStart();
+
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertTrue(mCaptureCallback.mVideo);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            CriteriaHelper.pollInstrumentationThread(() -> {
+                Criteria.checkThat(getMediaCaptureNotification(), Matchers.notNullValue());
+            });
+        }
+
+        stopStream();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            CriteriaHelper.pollInstrumentationThread(() -> {
+                Criteria.checkThat(getMediaCaptureNotification(), Matchers.nullValue());
+            });
+        }
+    }
+
+    /**
+     * Tests that the per-site permission, once granted, is remembered the next time a stream is
+     * requested.
+     */
+    @Test
+    @MediumTest
+    @DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/1107380
+    public void rememberPermission() throws Throwable {
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/weblayer/test/data/getusermedia.html"));
+
+        grantPermissionAndWaitForStreamToStart();
+
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertTrue(mCaptureCallback.mVideo);
+
+        stopStream();
+
+        // No permission prompt required the second time.
+        mCaptureCallback.mRequestedCountDown = new BoundedCountDownLatch(1);
+        mCaptureCallback.mStateCountDown = new BoundedCountDownLatch(1);
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/weblayer/test/data/getusermedia.html"));
+        mCaptureCallback.mRequestedCountDown.timedAwait();
+        mCaptureCallback.mStateCountDown.timedAwait();
+
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertTrue(mCaptureCallback.mVideo);
+    }
+
+    /**
+     * Tests that a site can request two parallel streams and both are stopped via {@link
+     * stopMediaCapturing}.
+     */
+    @Test
+    @MediumTest
+    @DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/1107380
+    public void twoStreams() throws Throwable {
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/weblayer/test/data/getusermedia2.html"));
+
+        // Audio stream.
+        grantPermissionAndWaitForStreamToStart();
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertFalse(mCaptureCallback.mVideo);
+
+        // Video stream.
+        grantPermissionAndWaitForStreamToStart();
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertTrue(mCaptureCallback.mVideo);
+
+        mCaptureCallback.mStateCountDown = new BoundedCountDownLatch(2);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getMediaCaptureController().stopMediaCapturing(); });
+        mCaptureCallback.mStateCountDown.timedAwait();
+        Assert.assertFalse(mCaptureCallback.mAudio);
+        Assert.assertFalse(mCaptureCallback.mVideo);
+    }
+
+    /**
+     * Tests that the notification posted for a tab will be updated if a second stream is started.
+     */
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    @DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/1107380
+    public void twoStreamsNotification() throws Throwable {
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/weblayer/test/data/getusermedia2.html"));
+
+        // Audio stream.
+        grantPermissionAndWaitForStreamToStart();
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertFalse(mCaptureCallback.mVideo);
+
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getMediaCaptureNotification(), Matchers.notNullValue());
+        });
+        Notification audioNotification = getMediaCaptureNotification();
+
+        // Video stream.
+        grantPermissionAndWaitForStreamToStart();
+        Assert.assertTrue(mCaptureCallback.mAudio);
+        Assert.assertTrue(mCaptureCallback.mVideo);
+
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Notification combinedNotification = getMediaCaptureNotification();
+            Criteria.checkThat(combinedNotification, Matchers.notNullValue());
+            Criteria.checkThat(combinedNotification.getSmallIcon().getResId(),
+                    Matchers.not(audioNotification.getSmallIcon().getResId()));
+        });
+
+        mCaptureCallback.mStateCountDown = new BoundedCountDownLatch(2);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getMediaCaptureController().stopMediaCapturing(); });
+        mCaptureCallback.mStateCountDown.timedAwait();
+        Assert.assertFalse(mCaptureCallback.mAudio);
+        Assert.assertFalse(mCaptureCallback.mVideo);
+
+        CriteriaHelper.pollInstrumentationThread(
+                () -> Criteria.checkThat(getMediaCaptureNotification(), Matchers.nullValue()));
+    }
+
+    private void grantPermissionAndWaitForStreamToStart() throws Throwable {
+        CriteriaHelper.pollInstrumentationThread(
+                () -> { return mTestWebLayer.isPermissionDialogShown(); });
+        mCaptureCallback.mRequestedCountDown = new BoundedCountDownLatch(1);
+        mCaptureCallback.mStateCountDown = new BoundedCountDownLatch(1);
+        mTestWebLayer.clickPermissionDialogButton(true);
+
+        mCaptureCallback.mRequestedCountDown.timedAwait();
+        mCaptureCallback.mStateCountDown.timedAwait();
+    }
+
+    private void stopStream() throws Throwable {
+        mCaptureCallback.mStateCountDown = new BoundedCountDownLatch(1);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getMediaCaptureController().stopMediaCapturing(); });
+        mCaptureCallback.mStateCountDown.timedAwait();
+        Assert.assertFalse(mCaptureCallback.mAudio);
+        Assert.assertFalse(mCaptureCallback.mVideo);
+    }
+
+    /**
+     * Retrieves the active media capture notification, or null if none exists.
+     * Asserts that at most one notification exists.
+     * {@link NotificationManager#getActiveNotifications()} is only available from M.
+     */
+    @RequiresApi(Build.VERSION_CODES.M)
+    private Notification getMediaCaptureNotification() {
+        StatusBarNotification notifications[];
+        try {
+            // Workaround for Android bug fixed in 34a80841cb8fa8cdbe6c584831f0e531618d331d.
+            notifications =
+                    ((NotificationManager) mActivity.getApplicationContext().getSystemService(
+                             Context.NOTIFICATION_SERVICE))
+                            .getActiveNotifications();
+        } catch (NullPointerException e) {
+            return null;
+        }
+        Notification notification = null;
+        for (StatusBarNotification statusBarNotification : notifications) {
+            if (statusBarNotification.getTag().equals("org.chromium.weblayer.webrtc.avstream")) {
+                Assert.assertNull(notification);
+                notification = statusBarNotification.getNotification();
+                Assert.assertNotNull(notification.getSmallIcon().loadDrawable(
+                        InstrumentationRegistry.getContext()));
+            }
+        }
+        return notification;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaRouterTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaRouterTest.java
new file mode 100644
index 0000000..9db80b9
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaRouterTest.java
@@ -0,0 +1,275 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+
+import androidx.test.filters.LargeTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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.DisabledTest;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Restriction;
+import org.chromium.content_public.browser.test.util.ClickUtils;
+import org.chromium.content_public.browser.test.util.TestTouchUtils;
+import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.ui.test.util.UiRestriction;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests of the Presentation API.
+ */
+@MinWebLayerVersion(88)
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ContentSwitches.DISABLE_GESTURE_REQUIREMENT_FOR_PRESENTATION})
+public class MediaRouterTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    private static final String TEST_PAGE = "media_router/basic_test.html";
+
+    private static final int SCRIPT_TIMEOUT_MS = 10000;
+    private static final int SCRIPT_RETRY_MS = 150;
+
+    private static final String TEST_SINK_NAME = "test-sink-1";
+
+    // Javascript snippets.
+    private static final String WAIT_DEVICE_SCRIPT = "waitUntilDeviceAvailable();";
+    private static final String START_PRESENTATION_SCRIPT = "startPresentation();";
+    private static final String TERMINATE_CONNECTION_SCRIPT =
+            "terminateConnectionAndWaitForStateChange();";
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+    }
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+    }
+
+    private void executeScriptAndWaitForResult(String script) throws Exception {
+        mActivityTestRule.executeScriptSync("lastExecutionResult = null", false);
+        mActivityTestRule.executeScriptSync(script, false);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            String result =
+                    mActivityTestRule.executeScriptAndExtractString("lastExecutionResult", false);
+            Criteria.checkThat(result, Matchers.is("passed"));
+        }, SCRIPT_TIMEOUT_MS, SCRIPT_RETRY_MS);
+    }
+
+    private void startPresentationAndSelectRoute() throws Exception {
+        // Request a presentation.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL(TEST_PAGE));
+        executeScriptAndWaitForResult(WAIT_DEVICE_SCRIPT);
+        executeScriptAndWaitForResult(START_PRESENTATION_SCRIPT);
+
+        // Verify the route selection dialog is showing and make a selection.
+        View testRouteButton = getTestWebLayer().getMediaRouteButton(TEST_SINK_NAME);
+        Assert.assertNotNull(testRouteButton);
+        ClickUtils.mouseSingleClickView(
+                InstrumentationRegistry.getInstrumentation(), testRouteButton);
+    }
+
+    private String verifyPresentationStarted() throws Exception {
+        // Verify in javascript that a presentation has started.
+        executeScriptAndWaitForResult("checkConnection();");
+        String connectionId =
+                mActivityTestRule.executeScriptAndExtractString("startedConnection.id", false);
+        Assert.assertFalse(connectionId.isEmpty());
+        String defaultRequestConnectionId = mActivityTestRule.executeScriptAndExtractString(
+                "defaultRequestConnectionId", false);
+        Assert.assertEquals(connectionId, defaultRequestConnectionId);
+        return connectionId;
+    }
+
+    void checkStartFailed(String errorName, String errorMessageSubstring) throws Exception {
+        String script =
+                String.format("checkStartFailed('%s', '%s');", errorName, errorMessageSubstring);
+        executeScriptAndWaitForResult(script);
+    }
+
+    /**
+     * Basic test where the page requests a route, the user selects a route, and a connection is
+     * started.
+     */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testBasic() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+        startPresentationAndSelectRoute();
+        verifyPresentationStarted();
+
+        executeScriptAndWaitForResult(TERMINATE_CONNECTION_SCRIPT);
+    }
+
+    /** Test of PresentationConnection.onmessage. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testSendAndOnMessage() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+        startPresentationAndSelectRoute();
+        verifyPresentationStarted();
+
+        executeScriptAndWaitForResult("sendMessageAndExpectResponse('foo');");
+    }
+
+    /** Test of PresentationConnection.onclose. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testOnClose() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/true,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+        startPresentationAndSelectRoute();
+        verifyPresentationStarted();
+
+        executeScriptAndWaitForResult("sendMessageAndExpectConnectionCloseOnError()");
+    }
+
+    /**
+     * Test that starting the presentation fails when there are no providers that support the given
+     * source.
+     */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testFailNoProvider() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/true, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+
+        startPresentationAndSelectRoute();
+        checkStartFailed("UnknownError", "No provider supports createRoute with source");
+    }
+
+    /** Tests route creation failure. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    @DisabledTest(message = "https://crbug.com/1181337")
+    public void testFailCreateRoute() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/"Unknown sink",
+                /*joinRouteErrorMessage=*/null);
+
+        startPresentationAndSelectRoute();
+        checkStartFailed("UnknownError", "Unknown sink");
+    }
+
+    /** Tests reconnecting to a presentation (joining a route) from a new tab. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testJoinRoute() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+
+        startPresentationAndSelectRoute();
+        String connectionId = verifyPresentationStarted();
+
+        Tab firstTab = mActivity.getTab();
+        Tab secondTab = runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getTab().getBrowser();
+            Tab tab = browser.createTab();
+            browser.setActiveTab(tab);
+            return tab;
+        });
+        mActivityTestRule.navigateAndWait(
+                secondTab, mActivityTestRule.getTestDataURL(TEST_PAGE), true);
+        executeScriptAndWaitForResult(String.format("reconnectConnection(\'%s\');", connectionId));
+        String reconnectedConnectionId =
+                mActivityTestRule.executeScriptAndExtractString("reconnectedConnection.id", false);
+        Assert.assertEquals(connectionId, reconnectedConnectionId);
+
+        runOnUiThreadBlocking(() -> { firstTab.getBrowser().setActiveTab(firstTab); });
+        executeScriptAndWaitForResult(TERMINATE_CONNECTION_SCRIPT);
+    }
+
+    /** Tests failure of reconnecting to a presentation (joining a route) from a new tab. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @LargeTest
+    public void testFailureToJoinRoute() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/"Unknown route");
+
+        startPresentationAndSelectRoute();
+        String connectionId = verifyPresentationStarted();
+
+        Tab secondTab = runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getTab().getBrowser();
+            Tab tab = browser.createTab();
+            browser.setActiveTab(tab);
+            return tab;
+        });
+        mActivityTestRule.navigateAndWait(
+                secondTab, mActivityTestRule.getTestDataURL(TEST_PAGE), true);
+        executeScriptAndWaitForResult(
+                String.format("reconnectConnectionAndExpectFailure(\'%s\');", connectionId));
+    }
+
+    /** Tests the user cancelling the media route selection process. */
+    @Test
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @Feature({"MediaRouter"})
+    @DisabledTest(message = "https://crbug.com/1144233")
+    @LargeTest
+    public void testFailStartCancelled() throws Exception {
+        getTestWebLayer().initializeMockMediaRouteProvider(/*closeRouteWithErrorOnSend=*/false,
+                /*disableIsSupportsSource=*/false, /*createRouteErrorMessage=*/null,
+                /*joinRouteErrorMessage=*/null);
+
+        // Request a presentation.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL(TEST_PAGE));
+        executeScriptAndWaitForResult(WAIT_DEVICE_SCRIPT);
+        executeScriptAndWaitForResult(START_PRESENTATION_SCRIPT);
+
+        // Verify the route selection dialog is showing but then dismiss it.
+        View testRouteButton = getTestWebLayer().getMediaRouteButton(TEST_SINK_NAME);
+        Assert.assertNotNull(testRouteButton);
+
+        // Click outside the dialog to dismiss it.
+        View topContents = mActivity.getTopContentsContainer();
+        TestTouchUtils.singleClick(
+                InstrumentationRegistry.getInstrumentation(), 1, topContents.getHeight() + 10);
+        checkStartFailed("NotAllowedError", "Dialog closed.");
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaSessionTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaSessionTest.java
new file mode 100644
index 0000000..f6e0fe7
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MediaSessionTest.java
@@ -0,0 +1,71 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.chromium.weblayer.R.id.weblayer_media_session_notification;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.filters.MediumTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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.MinAndroidSdkLevel;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that MediaSession works as expected.
+ */
+@CommandLineFlags.Add({"ignore-certificate-errors"})
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class MediaSessionTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
+    public void basic() throws Throwable {
+        mActivity = mActivityTestRule.launchShellWithUrl(
+                mActivityTestRule.getTestDataURL("media_session.html"));
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(getMediaSessionNotification(), Matchers.notNullValue());
+        });
+    }
+
+    /**
+     * Retrieves the active media session notification, or null if none exists.
+     * {@link NotificationManager#getActiveNotifications()} is only available from M.
+     */
+    @RequiresApi(Build.VERSION_CODES.M)
+    private Notification getMediaSessionNotification() {
+        StatusBarNotification notifications[] =
+                ((NotificationManager) mActivity.getApplicationContext().getSystemService(
+                         Context.NOTIFICATION_SERVICE))
+                        .getActiveNotifications();
+        for (StatusBarNotification statusBarNotification : notifications) {
+            if (statusBarNotification.getId() == weblayer_media_session_notification) {
+                return statusBarNotification.getNotification();
+            }
+        }
+        return null;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersion.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersion.java
new file mode 100644
index 0000000..288138b
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersion.java
@@ -0,0 +1,18 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MinWebLayerVersion {
+    int value() default 0;
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersionSkipCheck.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersionSkipCheck.java
new file mode 100644
index 0000000..2bd89c28
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/MinWebLayerVersionSkipCheck.java
@@ -0,0 +1,45 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.Log;
+import org.chromium.base.test.util.AnnotationProcessingUtils;
+import org.chromium.base.test.util.SkipCheck;
+
+/**
+ * Checks the WebLayer version against any specified minimum requirement.
+ */
+public class MinWebLayerVersionSkipCheck extends SkipCheck {
+    private static final String TAG = "MinWebLayerVersionSC";
+
+    /**
+     * If {@link MinWebLayerVersion} is present, checks its value
+     * against the WebLayer version.
+     *
+     * @param testCase The test to check.
+     * @return true if WebLayer's version is below the specified minimum.
+     */
+    @Override
+    public boolean shouldSkip(FrameworkMethod frameworkMethod) {
+        int minWebLayerVersion = 0;
+        for (MinWebLayerVersion m : AnnotationProcessingUtils.getAnnotations(
+                     frameworkMethod.getMethod(), MinWebLayerVersion.class)) {
+            minWebLayerVersion = Math.max(minWebLayerVersion, m.value());
+        }
+        String stringVersion = CommandLine.getInstance().getSwitchValue("impl-version", "-1");
+        int version = Integer.valueOf(stringVersion);
+        if (version != -1 && version < minWebLayerVersion) {
+            Log.i(TAG,
+                    "Test " + frameworkMethod.getDeclaringClass().getName() + "#"
+                            + frameworkMethod.getName() + " is not enabled at WebLayer version "
+                            + version + ".");
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
new file mode 100644
index 0000000..7118d7d9
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
@@ -0,0 +1,1752 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.webkit.WebResourceResponse;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+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.DisabledTest;
+import org.chromium.blink_public.common.BlinkFeatures;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.LoadError;
+import org.chromium.weblayer.NavigateParams;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
+import org.chromium.weblayer.NavigationController;
+import org.chromium.weblayer.NavigationState;
+import org.chromium.weblayer.NewTabCallback;
+import org.chromium.weblayer.Page;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabCallback;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.WebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Example test that just starts the weblayer shell.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class NavigationTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    // URLs used for base tests.
+    private static final String URL1 = "data:text,foo";
+    private static final String URL2 = "data:text,bar";
+    private static final String URL3 = "data:text,baz";
+    private static final String URL4 = "data:text,bat";
+    private static final String ENGLISH_PAGE = "english_page.html";
+    private static final String FRENCH_PAGE = "french_page.html";
+    private static final String STREAM_URL = "https://doesntreallyexist123.com/bar";
+    private static final String STREAM_HTML = "<html>foobar</html>";
+    private static final String STREAM_INNER_BODY = "foobar";
+
+    // A URL with a custom scheme/host that is handled by WebLayer Shell.
+    private static final String CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER =
+            "weblayer://weblayertest/intent";
+    // An intent that sends an url with a custom scheme that is handled by WebLayer Shell.
+    private static final String INTENT_TO_CUSTOM_SCHEME_URL =
+            "intent://weblayertest/intent#Intent;scheme=weblayer;"
+            + "action=android.intent.action.VIEW;end";
+
+    // An IntentInterceptor that simply drops intents to ensure that intent launches don't interfere
+    // with running of tests.
+    private class IntentInterceptor implements InstrumentationActivity.IntentInterceptor {
+        @Override
+        public void interceptIntent(Intent intent, int requestCode, Bundle options) {}
+    }
+
+    private <E extends Throwable> void assertThrows(Class<E> exceptionType, Runnable runnable) {
+        Throwable actualException = null;
+        try {
+            runnable.run();
+        } catch (Throwable e) {
+            actualException = e;
+        }
+        assertNotNull("Exception not thrown", actualException);
+        assertEquals(exceptionType, actualException.getClass());
+    }
+
+    private class Callback extends NavigationCallback {
+        public class NavigationCallbackHelper extends CallbackHelper {
+            private Uri mUri;
+            private boolean mIsSameDocument;
+            private int mHttpStatusCode;
+            private Map<String, String> mResponseHeaders;
+            private List<Uri> mRedirectChain;
+            private @LoadError int mLoadError;
+            private @NavigationState int mNavigationState;
+            private boolean mIsKnownProtocol;
+            private boolean mIsPageInitiatedNavigation;
+            private boolean mIsServedFromBackForwardCache;
+            private boolean mIsFormSubmission;
+            private Uri mReferrer;
+            private Page mPage;
+            private int mNavigationEntryOffset;
+            private boolean mWasFetchedFromCache;
+
+            public void notifyCalled(Navigation navigation) {
+                notifyCalled(navigation, false);
+            }
+
+            public void notifyCalled(Navigation navigation, boolean getPage) {
+                mUri = navigation.getUri();
+                mIsSameDocument = navigation.isSameDocument();
+                mHttpStatusCode = navigation.getHttpStatusCode();
+                mRedirectChain = navigation.getRedirectChain();
+                mLoadError = navigation.getLoadError();
+                mNavigationState = navigation.getState();
+                mIsPageInitiatedNavigation = navigation.isPageInitiated();
+                int majorVersion = TestThreadUtils.runOnUiThreadBlockingNoException(
+                        () -> WebLayer.getSupportedMajorVersion(mActivityTestRule.getActivity()));
+                if (majorVersion >= 89) {
+                    mIsKnownProtocol = navigation.isKnownProtocol();
+                    mIsServedFromBackForwardCache = navigation.isServedFromBackForwardCache();
+                }
+                if (majorVersion >= 90) {
+                    mIsFormSubmission = navigation.isFormSubmission();
+                    mReferrer = navigation.getReferrer();
+                    if (getPage) {
+                        mPage = navigation.getPage();
+                    }
+                }
+                if (majorVersion >= 91) {
+                    mResponseHeaders = navigation.getResponseHeaders();
+                }
+                if (majorVersion >= 92) {
+                    mNavigationEntryOffset = navigation.getNavigationEntryOffset();
+                }
+                if (majorVersion >= 102) {
+                    mWasFetchedFromCache = navigation.wasFetchedFromCache();
+                }
+                notifyCalled();
+            }
+
+            public void assertCalledWith(int currentCallCount, String uri) throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mUri.toString(), uri);
+            }
+
+            public void assertCalledWith(int currentCallCount, String uri, boolean isSameDocument)
+                    throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mUri.toString(), uri);
+                assertEquals(mIsSameDocument, isSameDocument);
+            }
+
+            public void assertCalledWith(int currentCallCount, List<Uri> redirectChain)
+                    throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mRedirectChain, redirectChain);
+            }
+
+            public void assertCalledWith(int currentCallCount, String uri, @LoadError int loadError)
+                    throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mUri.toString(), uri);
+                assertEquals(mLoadError, loadError);
+            }
+
+            public int getHttpStatusCode() {
+                return mHttpStatusCode;
+            }
+
+            public Map<String, String> getResponseHeaders() {
+                return mResponseHeaders;
+            }
+
+            @NavigationState
+            public int getNavigationState() {
+                return mNavigationState;
+            }
+
+            public boolean isKnownProtocol() {
+                return mIsKnownProtocol;
+            }
+
+            public boolean isServedFromBackForwardCache() {
+                return mIsServedFromBackForwardCache;
+            }
+
+            public boolean isPageInitiated() {
+                return mIsPageInitiatedNavigation;
+            }
+
+            public boolean isFormSubmission() {
+                return mIsFormSubmission;
+            }
+
+            public Uri getReferrer() {
+                return mReferrer;
+            }
+
+            public Page getPage() {
+                return mPage;
+            }
+
+            public int getNavigationEntryOffset() {
+                return mNavigationEntryOffset;
+            }
+
+            public boolean wasFetchedFromCache() {
+                return mWasFetchedFromCache;
+            }
+        }
+
+        public class UriCallbackHelper extends CallbackHelper {
+            private Uri mUri;
+
+            public void notifyCalled(Uri uri) {
+                mUri = uri;
+                notifyCalled();
+            }
+
+            public Uri getUri() {
+                return mUri;
+            }
+        }
+
+        public class PageCallbackHelper extends CallbackHelper {
+            private Page mPage;
+
+            public void notifyCalled(Page page) {
+                mPage = page;
+                notifyCalled();
+            }
+
+            public Page getPage() {
+                return mPage;
+            }
+
+            public void assertCalledWith(int currentCallCount, Page page) throws TimeoutException {
+                waitForCallback(currentCallCount);
+                assertEquals(mPage, page);
+            }
+        }
+
+        public class NavigationCallbackValueRecorder {
+            private List<String> mObservedValues =
+                    Collections.synchronizedList(new ArrayList<String>());
+
+            public void recordValue(String parameter) {
+                mObservedValues.add(parameter);
+            }
+
+            public List<String> getObservedValues() {
+                return mObservedValues;
+            }
+
+            public void waitUntilValueObserved(String expectation) {
+                CriteriaHelper.pollInstrumentationThread(
+                        () -> Criteria.checkThat(expectation, Matchers.isIn(mObservedValues)));
+            }
+        }
+
+        public class FirstContentfulPaintCallbackHelper extends CallbackHelper {
+            private long mNavigationStartMillis;
+            private long mFirstContentfulPaintMs;
+
+            public void notifyCalled(long navigationStartMillis, long firstContentfulPaintMs) {
+                mNavigationStartMillis = navigationStartMillis;
+                mFirstContentfulPaintMs = firstContentfulPaintMs;
+                notifyCalled();
+            }
+
+            public long getNavigationStartMillis() {
+                return mNavigationStartMillis;
+            }
+
+            public long getFirstContentfulPaintMs() {
+                return mFirstContentfulPaintMs;
+            }
+        }
+
+        public class LargestContentfulPaintCallbackHelper extends CallbackHelper {
+            private long mNavigationStartMillis;
+            private long mLargestContentfulPaintMs;
+
+            public void notifyCalled(long navigationStartMillis, long largestContentfulPaintMs) {
+                mNavigationStartMillis = navigationStartMillis;
+                mLargestContentfulPaintMs = largestContentfulPaintMs;
+                notifyCalled();
+            }
+
+            public long getNavigationStartMillis() {
+                return mNavigationStartMillis;
+            }
+
+            public long getLargestContentfulPaintMs() {
+                return mLargestContentfulPaintMs;
+            }
+        }
+
+        public class PageLanguageDeterminedCallbackHelper extends CallbackHelper {
+            private Page mPage;
+            private String mLanguage;
+
+            public void notifyCalled(Page page, String language) {
+                mPage = page;
+                mLanguage = language;
+                notifyCalled();
+            }
+
+            public Page getPage() {
+                return mPage;
+            }
+
+            public String getLanguage() {
+                return mLanguage;
+            }
+        }
+
+        public NavigationCallbackHelper onStartedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackHelper onRedirectedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackHelper onCompletedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackHelper onFailedCallback = new NavigationCallbackHelper();
+        public NavigationCallbackValueRecorder loadStateChangedCallback =
+                new NavigationCallbackValueRecorder();
+        public NavigationCallbackValueRecorder loadProgressChangedCallback =
+                new NavigationCallbackValueRecorder();
+        public CallbackHelper onFirstContentfulPaintCallback = new CallbackHelper();
+        public FirstContentfulPaintCallbackHelper onFirstContentfulPaint2Callback =
+                new FirstContentfulPaintCallbackHelper();
+        public LargestContentfulPaintCallbackHelper onLargestContentfulPaintCallback =
+                new LargestContentfulPaintCallbackHelper();
+        public UriCallbackHelper onOldPageNoLongerRenderedCallback = new UriCallbackHelper();
+        public PageCallbackHelper onPageDestroyedCallback = new PageCallbackHelper();
+        public PageLanguageDeterminedCallbackHelper onPageLanguageDeterminedCallback =
+                new PageLanguageDeterminedCallbackHelper();
+
+        @Override
+        public void onNavigationStarted(Navigation navigation) {
+            onStartedCallback.notifyCalled(navigation);
+        }
+
+        @Override
+        public void onNavigationRedirected(Navigation navigation) {
+            onRedirectedCallback.notifyCalled(navigation);
+        }
+
+        @Override
+        public void onNavigationCompleted(Navigation navigation) {
+            onCompletedCallback.notifyCalled(navigation, true);
+        }
+
+        @Override
+        public void onNavigationFailed(Navigation navigation) {
+            onFailedCallback.notifyCalled(navigation);
+        }
+
+        @Override
+        public void onFirstContentfulPaint() {
+            onFirstContentfulPaintCallback.notifyCalled();
+        }
+
+        @Override
+        public void onFirstContentfulPaint(
+                long navigationStartMillis, long firstContentfulPaintMs) {
+            onFirstContentfulPaint2Callback.notifyCalled(
+                    navigationStartMillis, firstContentfulPaintMs);
+        }
+
+        @Override
+        public void onLargestContentfulPaint(
+                long navigationStartMillis, long largestContentfulPaintMs) {
+            onLargestContentfulPaintCallback.notifyCalled(
+                    navigationStartMillis, largestContentfulPaintMs);
+        }
+
+        @Override
+        public void onOldPageNoLongerRendered(Uri newNavigationUri) {
+            onOldPageNoLongerRenderedCallback.notifyCalled(newNavigationUri);
+        }
+
+        @Override
+        public void onLoadStateChanged(boolean isLoading, boolean shouldShowLoadingUi) {
+            loadStateChangedCallback.recordValue(
+                    Boolean.toString(isLoading) + " " + Boolean.toString(shouldShowLoadingUi));
+        }
+
+        @Override
+        public void onLoadProgressChanged(double progress) {
+            loadProgressChangedCallback.recordValue(
+                    progress == 1 ? "load complete" : "load started");
+        }
+
+        @Override
+        public void onPageDestroyed(Page page) {
+            onPageDestroyedCallback.notifyCalled(page);
+        }
+
+        @Override
+        public void onPageLanguageDetermined(Page page, String language) {
+            onPageLanguageDeterminedCallback.notifyCalled(page, language);
+        }
+    }
+
+    private final Callback mCallback = new Callback();
+
+    @Test
+    @SmallTest
+    public void testNavigationEvents() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+
+        setNavigationCallback(activity);
+        int curStartedCount = mCallback.onStartedCallback.getCallCount();
+        int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+        int curOnFirstContentfulPaintCount =
+                mCallback.onFirstContentfulPaintCallback.getCallCount();
+
+        mActivityTestRule.navigateAndWait(URL2);
+
+        mCallback.onStartedCallback.assertCalledWith(curStartedCount, URL2);
+        mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL2);
+        mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
+        assertEquals(mCallback.onCompletedCallback.getHttpStatusCode(), 200);
+    }
+
+    @Test
+    @SmallTest
+    public void testOldPageNoLongerRendered() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int renderedCount = mCallback.onOldPageNoLongerRenderedCallback.getCallCount();
+        mActivityTestRule.navigateAndWait(URL2);
+        mCallback.onOldPageNoLongerRenderedCallback.waitForCallback(renderedCount);
+        assertEquals(Uri.parse(URL2), mCallback.onOldPageNoLongerRenderedCallback.getUri());
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadStateUpdates() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        mActivityTestRule.navigateAndWait(URL1);
+
+        /* Wait until the NavigationCallback is notified of load completion. */
+        mCallback.loadStateChangedCallback.waitUntilValueObserved("false false");
+        mCallback.loadProgressChangedCallback.waitUntilValueObserved("load complete");
+
+        /* Verify that the NavigationCallback was notified of load progress /before/ load
+         * completion.
+         */
+        int finishStateIndex =
+                mCallback.loadStateChangedCallback.getObservedValues().indexOf("false false");
+        int finishProgressIndex =
+                mCallback.loadProgressChangedCallback.getObservedValues().indexOf("load complete");
+        int startStateIndex =
+                mCallback.loadStateChangedCallback.getObservedValues().lastIndexOf("true true");
+        int startProgressIndex =
+                mCallback.loadProgressChangedCallback.getObservedValues().lastIndexOf(
+                        "load started");
+
+        assertNotEquals(startStateIndex, -1);
+        assertNotEquals(startProgressIndex, -1);
+        assertNotEquals(finishStateIndex, -1);
+        assertNotEquals(finishProgressIndex, -1);
+
+        assertTrue(startStateIndex < finishStateIndex);
+        assertTrue(startProgressIndex < finishProgressIndex);
+    }
+
+    @Test
+    @SmallTest
+    public void testReplace() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        final NavigateParams params =
+                new NavigateParams.Builder().setShouldReplaceCurrentEntry(true).build();
+        navigateAndWaitForCompletion(URL2,
+                ()
+                        -> activity.getTab().getNavigationController().navigate(
+                                Uri.parse(URL2), params));
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            assertFalse(navigationController.canGoForward());
+            assertFalse(navigationController.canGoBack());
+            assertEquals(1, navigationController.getNavigationListSize());
+        });
+
+        // Verify getter works as expected.
+        assertTrue(params.getShouldReplaceCurrentEntry());
+
+        // Verify that a default NavigateParams does not replace.
+        final NavigateParams params2 = new NavigateParams();
+        navigateAndWaitForCompletion(URL3,
+                ()
+                        -> activity.getTab().getNavigationController().navigate(
+                                Uri.parse(URL3), params2));
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            assertFalse(navigationController.canGoForward());
+            assertTrue(navigationController.canGoBack());
+            assertEquals(2, navigationController.getNavigationListSize());
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testGoBackAndForward() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait(URL2);
+        mActivityTestRule.navigateAndWait(URL3);
+
+        NavigationController navigationController =
+                runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
+
+        navigateAndWaitForCompletion(URL2, () -> {
+            assertTrue(navigationController.canGoBack());
+            navigationController.goBack();
+        });
+
+        navigateAndWaitForCompletion(URL1, () -> {
+            assertTrue(navigationController.canGoBack());
+            navigationController.goBack();
+        });
+
+        navigateAndWaitForCompletion(URL2, () -> {
+            assertFalse(navigationController.canGoBack());
+            assertTrue(navigationController.canGoForward());
+            navigationController.goForward();
+        });
+
+        navigateAndWaitForCompletion(URL3, () -> {
+            assertTrue(navigationController.canGoForward());
+            navigationController.goForward();
+        });
+
+        runOnUiThreadBlocking(() -> { assertFalse(navigationController.canGoForward()); });
+    }
+
+    @Test
+    @SmallTest
+    public void testGoToIndex() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait(URL2);
+        mActivityTestRule.navigateAndWait(URL3);
+        mActivityTestRule.navigateAndWait(URL4);
+
+        // Navigate back to the 2nd url.
+        assertEquals(URL2, goToIndexAndReturnUrl(activity.getTab(), 1));
+
+        // Navigate forwards to the 4th url.
+        assertEquals(URL4, goToIndexAndReturnUrl(activity.getTab(), 3));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetNavigationEntryTitle() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(
+                "data:text/html,<head><title>Page A</title></head>");
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait("data:text/html,<head><title>Page B</title></head>");
+        mActivityTestRule.navigateAndWait("data:text/html,<head><title>Page C</title></head>");
+
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            assertEquals("Page A", navigationController.getNavigationEntryTitle(0));
+            assertEquals("Page B", navigationController.getNavigationEntryTitle(1));
+            assertEquals("Page C", navigationController.getNavigationEntryTitle(2));
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testSameDocument() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+
+        mActivityTestRule.executeScriptSync(
+                "history.pushState(null, '', '#bar');", true /* useSeparateIsolate */);
+
+        mCallback.onCompletedCallback.assertCalledWith(
+                curCompletedCount, "data:text,foo#bar", true);
+    }
+
+    @Test
+    @SmallTest
+    public void testReload() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        navigateAndWaitForCompletion(
+                URL1, () -> { activity.getTab().getNavigationController().reload(); });
+    }
+
+    @Test
+    @SmallTest
+    public void testStop() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curFailedCount = mCallback.onFailedCallback.getCallCount();
+
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationStarted(Navigation navigation) {
+                    navigationController.stop();
+                }
+            });
+            navigationController.navigate(Uri.parse(URL2));
+        });
+
+        mCallback.onFailedCallback.assertCalledWith(curFailedCount, URL2);
+    }
+
+    @Test
+    @SmallTest
+    public void testRedirect() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curRedirectedCount = mCallback.onRedirectedCallback.getCallCount();
+
+        String finalUrl = mActivityTestRule.getTestServer().getURL("/echo");
+        String url = mActivityTestRule.getTestServer().getURL("/server-redirect?" + finalUrl);
+        navigateAndWaitForCompletion(finalUrl,
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+
+        mCallback.onRedirectedCallback.assertCalledWith(
+                curRedirectedCount, Arrays.asList(Uri.parse(url), Uri.parse(finalUrl)));
+    }
+
+    /**
+     * This test verifies that calling getPage() from within onNavigationFailed for a
+     * navigation that results in an error page returns a non-null Page object, and that an
+     * onPageDestroyed() callback is triggered for that page when the user navigates away.
+     */
+    @MinWebLayerVersion(93)
+    @Test
+    @SmallTest
+    public void testPageCallbacksForNavigationResultingInErrorPage() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        CallbackHelper navigationFailedCallbackHelper = new CallbackHelper();
+        CallbackHelper pageDestroyedCallbackHelper = new CallbackHelper();
+        final Page[] pageForFailedNavigation = {null};
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationFailed(Navigation navigation) {
+                    assertTrue(navigation.isErrorPage());
+                    pageForFailedNavigation[0] = navigation.getPage();
+                    assertNotNull(pageForFailedNavigation[0]);
+                    navigationFailedCallbackHelper.notifyCalled();
+                }
+                @Override
+                public void onPageDestroyed(Page page) {
+                    assertEquals(pageForFailedNavigation[0], page);
+                    navigationController.unregisterNavigationCallback(this);
+                    pageDestroyedCallbackHelper.notifyCalled();
+                }
+            });
+        });
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Do a navigation that will result in an error page.
+            activity.getTab().getNavigationController().navigate(
+                    Uri.parse("http://localhost:7/non_existent"));
+        });
+        navigationFailedCallbackHelper.waitForFirst();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(URL1)); });
+        pageDestroyedCallbackHelper.waitForFirst();
+    }
+
+    /**
+     * This is a regression test for crbug.com/1233480, adapted for a change in
+     * //content to have such navigations commit rather than fail. It also
+     * should not crash nor throw an exception.
+     */
+    @MinWebLayerVersion(98)
+    @Test
+    @SmallTest
+    @CommandLineFlags.Add("enable-features=" + BlinkFeatures.INITIAL_NAVIGATION_ENTRY)
+    public void testInitialRendererInitiatedNavigationToAboutBlankSucceeds() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+
+        // Setup a callback for when the navigation in a new tab fails.
+        CallbackHelper callbackHelper = new CallbackHelper();
+        NewTabCallback newTabCallback = new NewTabCallback() {
+            @Override
+            public void onNewTab(Tab tab, int mode) {
+                NavigationController navigationController = tab.getNavigationController();
+                navigationController.registerNavigationCallback(new NavigationCallback() {
+                    @Override
+                    public void onNavigationCompleted(Navigation navigation) {
+                        assertEquals(NavigationState.COMPLETE, navigation.getState());
+                        // There should be a valid page for this navigation.
+                        assertNotNull(navigation.getPage());
+                        navigationController.unregisterNavigationCallback(this);
+                        callbackHelper.notifyCalled();
+                    }
+                });
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getBrowser().getActiveTab().setNewTabCallback(newTabCallback); });
+
+        // Click on the document to invoke window.open(), which results in a renderer-initiated
+        // navigation to about:blank in a new tab.
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = () => window.open();", true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    public void testNavigationList() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait(URL2);
+        mActivityTestRule.navigateAndWait(URL3);
+
+        NavigationController navigationController =
+                runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
+
+        runOnUiThreadBlocking(() -> {
+            assertEquals(3, navigationController.getNavigationListSize());
+            assertEquals(2, navigationController.getNavigationListCurrentIndex());
+            assertEquals(URL1, navigationController.getNavigationEntryDisplayUri(0).toString());
+            assertEquals(URL2, navigationController.getNavigationEntryDisplayUri(1).toString());
+            assertEquals(URL3, navigationController.getNavigationEntryDisplayUri(2).toString());
+        });
+
+        navigateAndWaitForCompletion(URL2, () -> { navigationController.goBack(); });
+
+        runOnUiThreadBlocking(() -> {
+            assertEquals(3, navigationController.getNavigationListSize());
+            assertEquals(1, navigationController.getNavigationListCurrentIndex());
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadError() throws Exception {
+        String url = mActivityTestRule.getTestDataURL("non_empty404.html");
+
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        setNavigationCallback(activity);
+
+        int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+
+        // navigateAndWait() expects a success code, so it won't work here.
+        runOnUiThreadBlocking(
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+
+        mCallback.onCompletedCallback.assertCalledWith(
+                curCompletedCount, url, LoadError.HTTP_CLIENT_ERROR);
+        assertEquals(mCallback.onCompletedCallback.getHttpStatusCode(), 404);
+        assertEquals(mCallback.onCompletedCallback.getNavigationState(), NavigationState.COMPLETE);
+    }
+
+    @MinWebLayerVersion(89)
+    @Test
+    @SmallTest
+    public void testIsKnownProtocol() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+        setNavigationCallback(activity);
+
+        // Test various known protocol cases.
+        String httpUrl = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.navigateAndWait(httpUrl);
+        assertEquals(true, mCallback.onStartedCallback.isKnownProtocol());
+        assertEquals(true, mCallback.onCompletedCallback.isKnownProtocol());
+
+        mActivityTestRule.navigateAndWait("about:blank");
+        assertEquals(true, mCallback.onStartedCallback.isKnownProtocol());
+        assertEquals(true, mCallback.onCompletedCallback.isKnownProtocol());
+
+        String dataUrl = "data:text,foo";
+        mActivityTestRule.navigateAndWait(dataUrl);
+        assertEquals(true, mCallback.onStartedCallback.isKnownProtocol());
+        assertEquals(true, mCallback.onCompletedCallback.isKnownProtocol());
+
+        // Test external protocol cases.
+        mActivityTestRule.navigateAndWaitForFailure(activity.getTab(), INTENT_TO_CUSTOM_SCHEME_URL,
+                /*waitForPaint=*/false);
+        assertEquals(false, mCallback.onStartedCallback.isKnownProtocol());
+        assertEquals(false, mCallback.onFailedCallback.isKnownProtocol());
+
+        mActivityTestRule.navigateAndWaitForFailure(activity.getTab(),
+                CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER,
+                /*waitForPaint=*/false);
+        assertEquals(false, mCallback.onStartedCallback.isKnownProtocol());
+        assertEquals(false, mCallback.onFailedCallback.isKnownProtocol());
+    }
+
+    @Test
+    @SmallTest
+    public void testRepostConfirmation() throws Exception {
+        // Load a page with a form.
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
+        assertNotNull(activity);
+        setNavigationCallback(activity);
+
+        // Touch the page; this should submit the form.
+        int currentCallCount = mCallback.onCompletedCallback.getCallCount();
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+        String targetUrl = mActivityTestRule.getTestDataURL("simple_page.html");
+        mCallback.onCompletedCallback.assertCalledWith(currentCallCount, targetUrl);
+
+        // Make sure a tab modal shows after we attempt a reload.
+        Boolean isTabModalShowingResult[] = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTabModalStateChanged(boolean isTabModalShowing) {
+                    isTabModalShowingResult[0] = isTabModalShowing;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+            tab.getNavigationController().reload();
+        });
+
+        callbackHelper.waitForFirst();
+        assertTrue(isTabModalShowingResult[0]);
+    }
+
+    private void setNavigationCallback(InstrumentationActivity activity) {
+        runOnUiThreadBlocking(
+                ()
+                        -> activity.getTab().getNavigationController().registerNavigationCallback(
+                                mCallback));
+    }
+
+    private void registerNavigationCallback(NavigationCallback callback) {
+        runOnUiThreadBlocking(()
+                                      -> mActivityTestRule.getActivity()
+                                                 .getTab()
+                                                 .getNavigationController()
+                                                 .registerNavigationCallback(callback));
+    }
+
+    private void unregisterNavigationCallback(NavigationCallback callback) {
+        runOnUiThreadBlocking(()
+                                      -> mActivityTestRule.getActivity()
+                                                 .getTab()
+                                                 .getNavigationController()
+                                                 .unregisterNavigationCallback(callback));
+    }
+
+    private void navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable)
+            throws Exception {
+        int currentCallCount = mCallback.onCompletedCallback.getCallCount();
+        runOnUiThreadBlocking(navigateRunnable);
+        mCallback.onCompletedCallback.assertCalledWith(currentCallCount, expectedUrl);
+    }
+
+    private String goToIndexAndReturnUrl(Tab tab, int index) throws Exception {
+        NavigationController navigationController =
+                runOnUiThreadBlocking(() -> tab.getNavigationController());
+
+        final BoundedCountDownLatch navigationComplete = new BoundedCountDownLatch(1);
+        final AtomicReference<String> navigationUrl = new AtomicReference<String>();
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationCompleted(Navigation navigation) {
+                navigationComplete.countDown();
+                navigationUrl.set(navigation.getUri().toString());
+            }
+        };
+
+        runOnUiThreadBlocking(() -> {
+            navigationController.registerNavigationCallback(navigationCallback);
+            navigationController.goToIndex(index);
+        });
+
+        navigationComplete.timedAwait();
+
+        runOnUiThreadBlocking(
+                () -> { navigationController.unregisterNavigationCallback(navigationCallback); });
+
+        return navigationUrl.get();
+    }
+
+    @Test
+    @SmallTest
+    public void testStopFromOnNavigationStarted() throws Exception {
+        final InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        final BoundedCountDownLatch doneLatch = new BoundedCountDownLatch(1);
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                activity.getTab().getNavigationController().stop();
+                doneLatch.countDown();
+            }
+        };
+        runOnUiThreadBlocking(() -> {
+            NavigationController controller = activity.getTab().getNavigationController();
+            controller.registerNavigationCallback(navigationCallback);
+            controller.navigate(Uri.parse(URL1));
+        });
+        doneLatch.timedAwait();
+    }
+
+    // NavigationCallback implementation that sets a header in either start or redirect.
+    private static final class HeaderSetter extends NavigationCallback {
+        private final String mName;
+        private final String mValue;
+        private final boolean mInStart;
+        public boolean mGotIllegalArgumentException;
+
+        HeaderSetter(String name, String value, boolean inStart) {
+            mName = name;
+            mValue = value;
+            mInStart = inStart;
+        }
+
+        @Override
+        public void onNavigationStarted(Navigation navigation) {
+            if (mInStart) applyHeader(navigation);
+        }
+
+        @Override
+        public void onNavigationRedirected(Navigation navigation) {
+            if (!mInStart) applyHeader(navigation);
+        }
+
+        private void applyHeader(Navigation navigation) {
+            try {
+                navigation.setRequestHeader(mName, mValue);
+            } catch (IllegalArgumentException e) {
+                mGotIllegalArgumentException = true;
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRequestHeaderInStart() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        String headerName = "header";
+        String headerValue = "value";
+        HeaderSetter setter = new HeaderSetter(headerName, headerValue, true);
+        registerNavigationCallback(setter);
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url);
+        assertFalse(setter.mGotIllegalArgumentException);
+        assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRequestHeaderInRedirect() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        String headerName = "header";
+        String headerValue = "value";
+        HeaderSetter setter = new HeaderSetter(headerName, headerValue, false);
+        registerNavigationCallback(setter);
+        // The destination of the redirect.
+        String finalUrl = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        // The url that redirects to |finalUrl|.
+        String redirectingUrl = testServer.setRedirect("/redirect.html", finalUrl);
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        NavigationWaiter waiter = new NavigationWaiter(finalUrl, tab, false, false);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(redirectingUrl)); });
+        waiter.waitForNavigation();
+        assertFalse(setter.mGotIllegalArgumentException);
+        assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRequestHeaderThrowsExceptionInCompleted() throws Exception {
+        mActivityTestRule.launchShellWithUrl(null);
+        boolean gotCompleted[] = new boolean[1];
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationCompleted(Navigation navigation) {
+                gotCompleted[0] = true;
+                boolean gotException = false;
+                try {
+                    navigation.setRequestHeader("name", "value");
+                } catch (IllegalStateException e) {
+                    gotException = true;
+                }
+                assertTrue(gotException);
+            }
+        };
+        registerNavigationCallback(navigationCallback);
+        mActivityTestRule.navigateAndWait(URL1);
+        assertTrue(gotCompleted[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRequestHeaderThrowsExceptionWithInvalidValue() throws Exception {
+        mActivityTestRule.launchShellWithUrl(null);
+        HeaderSetter setter = new HeaderSetter("name", "\0", true);
+        registerNavigationCallback(setter);
+        mActivityTestRule.navigateAndWait(URL1);
+        assertTrue(setter.mGotIllegalArgumentException);
+    }
+
+    // NavigationCallback implementation that sets the user-agent string in onNavigationStarted().
+    private static final class UserAgentSetter extends NavigationCallback {
+        private final String mValue;
+        public boolean mGotIllegalStateException;
+
+        UserAgentSetter(String value) {
+            mValue = value;
+        }
+
+        @Override
+        public void onNavigationStarted(Navigation navigation) {
+            try {
+                navigation.setUserAgentString(mValue);
+            } catch (IllegalStateException e) {
+                mGotIllegalStateException = true;
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetUserAgentString() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        String customUserAgent = "custom-ua";
+        UserAgentSetter setter = new UserAgentSetter(customUserAgent);
+        registerNavigationCallback(setter);
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url);
+        String actualUserAgent = testServer.getLastRequest("/ok.html").headerValue("User-Agent");
+        assertEquals(customUserAgent, actualUserAgent);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    public void testCantUsePerNavigationAndDesktopMode() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        UserAgentSetter setter = new UserAgentSetter("foo");
+        registerNavigationCallback(setter);
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
+        mActivityTestRule.navigateAndWait(url);
+        assertTrue(setter.mGotIllegalStateException);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    public void testDesktopMode() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
+        mActivityTestRule.navigateAndWait(url);
+        String actualUserAgent = testServer.getLastRequest("/ok.html").headerValue("User-Agent");
+        assertFalse(actualUserAgent.contains("Android"));
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    public void testDesktopModeSticks() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        String url2 = testServer.setResponse("/ok2.html", "<html>ok</html>", null);
+        runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
+        mActivityTestRule.navigateAndWait(url);
+        mActivityTestRule.navigateAndWait(url2);
+        String actualUserAgent = testServer.getLastRequest("/ok2.html").headerValue("User-Agent");
+        assertFalse(actualUserAgent.contains("Android"));
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88)
+    public void testDesktopModeGetter() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+
+        UserAgentSetter setter = new UserAgentSetter("foo");
+        registerNavigationCallback(setter);
+        mActivityTestRule.navigateAndWait(URL1);
+        unregisterNavigationCallback(setter);
+        runOnUiThreadBlocking(
+                () -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
+
+        runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
+        mActivityTestRule.navigateAndWait(URL2);
+        runOnUiThreadBlocking(() -> { assertTrue(activity.getTab().isDesktopUserAgentEnabled()); });
+
+        navigateAndWaitForCompletion(
+                URL1, () -> activity.getTab().getNavigationController().goBack());
+        runOnUiThreadBlocking(
+                () -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
+    }
+
+    @Test
+    @SmallTest
+    public void testSkippedNavigationEntry() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+        mActivityTestRule.executeScriptSync(
+                "history.pushState(null, '', '#foo');", true /* useSeparateIsolate */);
+        mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL1 + "#foo", true);
+
+        curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+        mActivityTestRule.executeScriptSync(
+                "history.pushState(null, '', '#bar');", true /* useSeparateIsolate */);
+        mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL1 + "#bar", true);
+
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            int currentIndex = navigationController.getNavigationListCurrentIndex();
+            // Should skip the two previous same document entries, but not the most recent.
+            assertFalse(navigationController.isNavigationEntrySkippable(currentIndex));
+            assertTrue(navigationController.isNavigationEntrySkippable(currentIndex - 1));
+            assertTrue(navigationController.isNavigationEntrySkippable(currentIndex - 2));
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testIndexOutOfBounds() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        runOnUiThreadBlocking(() -> {
+            NavigationController controller = activity.getTab().getNavigationController();
+            assertIndexOutOfBoundsException(() -> controller.goBack());
+            assertIndexOutOfBoundsException(() -> controller.goForward());
+            assertIndexOutOfBoundsException(() -> controller.goToIndex(10));
+            assertIndexOutOfBoundsException(() -> controller.getNavigationEntryDisplayUri(10));
+            assertIndexOutOfBoundsException(() -> controller.getNavigationEntryTitle(10));
+            assertIndexOutOfBoundsException(() -> controller.isNavigationEntrySkippable(10));
+        });
+    }
+
+    private static void assertIndexOutOfBoundsException(Runnable runnable) {
+        try {
+            runnable.run();
+        } catch (IndexOutOfBoundsException e) {
+            // Expected exception.
+            return;
+        }
+        Assert.fail("Expected IndexOutOfBoundsException.");
+    }
+
+    @Test
+    @SmallTest
+    public void testPageInitiated() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        String initialUrl = mActivityTestRule.getTestDataURL("simple_page4.html");
+        mActivityTestRule.navigateAndWait(initialUrl);
+        String refreshUrl = mActivityTestRule.getTestDataURL("simple_page.html");
+        mCallback.onCompletedCallback.assertCalledWith(
+                mCallback.onCompletedCallback.getCallCount(), refreshUrl);
+        assertTrue(mCallback.onCompletedCallback.isPageInitiated());
+    }
+
+    @Test
+    @SmallTest
+    public void testPageInitiatedFromClient() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+        mActivityTestRule.navigateAndWait(URL2);
+        assertFalse(mCallback.onStartedCallback.isPageInitiated());
+    }
+
+    // Verifies the following sequence doesn't crash:
+    // 1. create a new background tab.
+    // 2. show modal dialog.
+    // 3. destroy tab with modal dialog.
+    // 4. switch to background tab created in step 1.
+    // This is a regression test for https://crbug.com/1121388.
+    @Test
+    @SmallTest
+    public void testDestroyTabWithModalDialog() throws Exception {
+        // Load a page with a form.
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
+        assertNotNull(activity);
+        setNavigationCallback(activity);
+
+        // Touch the page; this should submit the form.
+        int currentCallCount = mCallback.onCompletedCallback.getCallCount();
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+        String targetUrl = mActivityTestRule.getTestDataURL("simple_page.html");
+        mCallback.onCompletedCallback.assertCalledWith(currentCallCount, targetUrl);
+
+        Tab secondTab = runOnUiThreadBlocking(() -> activity.getTab().getBrowser().createTab());
+        // Make sure a tab modal shows after we attempt a reload.
+        Boolean isTabModalShowingResult[] = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            Browser browser = tab.getBrowser();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTabModalStateChanged(boolean isTabModalShowing) {
+                    tab.unregisterTabCallback(this);
+                    isTabModalShowingResult[0] = isTabModalShowing;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+
+            browser.registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onTabRemoved(Tab tab) {
+                    browser.unregisterTabListCallback(this);
+                    browser.setActiveTab(secondTab);
+                }
+            });
+            tab.getNavigationController().reload();
+        });
+
+        callbackHelper.waitForFirst();
+        runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            tab.getBrowser().destroyTab(tab);
+        });
+    }
+
+    /**
+     * This test verifies calling destroyTab() from within onNavigationFailed doesn't crash.
+     */
+    @Test
+    @SmallTest
+    public void testDestroyTabInNavigationFailed() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationFailed(Navigation navigation) {
+                    navigationController.unregisterNavigationCallback(this);
+                    Tab tab = activity.getTab();
+                    tab.getBrowser().destroyTab(tab);
+                    callbackHelper.notifyCalled();
+                }
+            });
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getTab().getNavigationController().navigate(
+                    Uri.parse("http://localhost:7/non_existent"));
+        });
+        callbackHelper.waitForFirst();
+    }
+
+    private void navigateToStream(InstrumentationActivity activity, String mimeType,
+            String cacheControl, String html) throws Exception {
+        int curOnFirstContentfulPaintCount =
+                mCallback.onFirstContentfulPaintCallback.getCallCount();
+        InputStream stream = new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8));
+        WebResourceResponse response = new WebResourceResponse(mimeType, "UTF-8", stream);
+        if (cacheControl != null) {
+            Map<String, String> headers = new HashMap<>();
+            headers.put("Cache-Control", cacheControl);
+            response.setResponseHeaders(headers);
+        }
+
+        final NavigateParams params = new NavigateParams.Builder().setResponse(response).build();
+        navigateAndWaitForCompletion(STREAM_URL,
+                ()
+                        -> activity.getTab().getNavigationController().navigate(
+                                Uri.parse(STREAM_URL), params));
+        mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
+    }
+
+    private void navigateToStream(InstrumentationActivity activity, String mimeType,
+            String cacheControl) throws Exception {
+        navigateToStream(activity, mimeType, cacheControl, STREAM_HTML);
+    }
+
+    private void assertStreamContent() throws Exception {
+        assertEquals(STREAM_INNER_BODY,
+                mActivityTestRule.executeScriptAndExtractString("document.body.innerText"));
+    }
+
+    @Test
+    @SmallTest
+    public void testWebResponse() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        // The code asserts that when InputStreams are used that the stock URL bar is not visible.
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "text/html", null);
+        assertStreamContent();
+    }
+
+    @Test
+    @SmallTest
+    public void testWebResponseMimeSniff() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "", null);
+        assertStreamContent();
+    }
+
+    @Test
+    @SmallTest
+    public void testWebResponseNoCacheControl() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "text/html", null);
+
+        mActivityTestRule.navigateAndWait(URL1);
+
+        int curFailedCount = mCallback.onFailedCallback.getCallCount();
+        runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
+        mCallback.onFailedCallback.assertCalledWith(
+                curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
+    }
+
+    @Test
+    @SmallTest
+    public void testWebResponseCached() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "text/html", "private, max-age=60");
+
+        // Now check that the data can be reused from the cache if it had the correct headers.
+        mActivityTestRule.navigateAndWait(URL1);
+        int curOnFirstContentfulPaintCount =
+                mCallback.onFirstContentfulPaintCallback.getCallCount();
+        navigateAndWaitForCompletion(
+                STREAM_URL, () -> { activity.getTab().getNavigationController().goBack(); });
+        mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
+        assertStreamContent();
+    }
+
+    @Test
+    @SmallTest
+    public void testWebResponseCachedWithSniffedMimeType() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "", "private, max-age=60");
+
+        mActivityTestRule.navigateAndWait(URL1);
+
+        int curOnFirstContentfulPaintCount =
+                mCallback.onFirstContentfulPaintCallback.getCallCount();
+        navigateAndWaitForCompletion(
+                STREAM_URL, () -> { activity.getTab().getNavigationController().goBack(); });
+        mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
+        assertStreamContent();
+    }
+
+    @DisabledTest(message = "https://crbug.com/1271989")
+    @Test
+    @SmallTest
+    public void testWebResponseNoStore() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "text/html", "no-store");
+
+        mActivityTestRule.navigateAndWait(URL1);
+
+        int curFailedCount = mCallback.onFailedCallback.getCallCount();
+        runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
+        mCallback.onFailedCallback.assertCalledWith(
+                curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
+    }
+
+    @DisabledTest(message = "https://crbug.com/1238151")
+    @Test
+    @SmallTest
+    public void testWebResponseExpired() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        navigateToStream(activity, "text/html", "private, max-age=2");
+
+        Thread.sleep(5000);
+
+        mActivityTestRule.navigateAndWait(URL1);
+
+        int curFailedCount = mCallback.onFailedCallback.getCallCount();
+        runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
+        mCallback.onFailedCallback.assertCalledWith(
+                curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
+    }
+
+    // Verifies that a request which uses a stream can still set the user agent that is used for
+    // subresources.
+    @Test
+    @SmallTest
+    // The flags are necessary for the following reasons:
+    // ignore-certificate-errors: TestWebServer doesn't have a real cert.
+    @CommandLineFlags.Add({"ignore-certificate-errors"})
+    public void testWebResponseWithUserAgent() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
+        setNavigationCallback(activity);
+
+        // Avoid mixed http/https errors since the stream url is https.
+        TestWebServer testServer = TestWebServer.startSsl();
+        String scriptUrl = testServer.setResponse("/foo.js", "", null);
+        String streamHtml = "<html><script src='" + scriptUrl + "'/>bar</html>";
+
+        String customUserAgent = "custom-ua";
+        UserAgentSetter setter = new UserAgentSetter(customUserAgent);
+        registerNavigationCallback(setter);
+
+        navigateToStream(activity, "", null, streamHtml);
+
+        // Ensure the script is fetched.
+        CriteriaHelper.pollInstrumentationThread(
+                ()
+                        -> Criteria.checkThat(
+                                testServer.getLastRequest("/foo.js"), Matchers.notNullValue()));
+
+        String actualUserAgent = testServer.getLastRequest("/foo.js").headerValue("User-Agent");
+        assertEquals(customUserAgent, actualUserAgent);
+    }
+
+    @MinWebLayerVersion(88)
+    @Test
+    @SmallTest
+    public void testOnFirstContentfulPaintTiming() throws Exception {
+        long activityStartTimeMs = SystemClock.uptimeMillis();
+
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+
+        int count = mCallback.onFirstContentfulPaint2Callback.getCallCount();
+        mActivityTestRule.navigateAndWait(url);
+        mCallback.onFirstContentfulPaint2Callback.waitForCallback(count);
+
+        long navigationStart = mCallback.onFirstContentfulPaint2Callback.getNavigationStartMillis();
+        long current = SystemClock.uptimeMillis();
+        Assert.assertTrue(navigationStart <= current);
+        Assert.assertTrue(navigationStart >= activityStartTimeMs);
+
+        long firstContentfulPaint =
+                mCallback.onFirstContentfulPaint2Callback.getFirstContentfulPaintMs();
+        Assert.assertTrue(firstContentfulPaint <= (current - navigationStart));
+    }
+
+    @MinWebLayerVersion(88)
+    @Test
+    @SmallTest
+    public void testOnLargestContentfulPaintTiming() throws Exception {
+        long activityStartTimeMs = SystemClock.uptimeMillis();
+
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+
+        int count = mCallback.onLargestContentfulPaintCallback.getCallCount();
+        mActivityTestRule.navigateAndWait(url);
+
+        // Navigate to a new page, as metrics like LCP are only reported at the end of the page load
+        // lifetime.
+        mActivityTestRule.navigateAndWait("about:blank");
+        mCallback.onLargestContentfulPaintCallback.waitForCallback(count);
+
+        long navigationStart =
+                mCallback.onLargestContentfulPaintCallback.getNavigationStartMillis();
+        long current = SystemClock.uptimeMillis();
+        Assert.assertTrue(navigationStart <= current);
+        Assert.assertTrue(navigationStart >= activityStartTimeMs);
+
+        long largestContentfulPaint =
+                mCallback.onLargestContentfulPaintCallback.getLargestContentfulPaintMs();
+        Assert.assertTrue(largestContentfulPaint <= (current - navigationStart));
+    }
+
+    /* Disable BackForwardCacheMemoryControls to allow BackForwardCache for all devices regardless
+     * of their memory. */
+    @MinWebLayerVersion(89)
+    @Test
+    @SmallTest
+    @CommandLineFlags.
+    Add({"enable-features=BackForwardCache", "disable-features=BackForwardCacheMemoryControls"})
+    public void testServedFromBackForwardCache() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+
+        String url = mActivityTestRule.getTestServer().getURL("/echo");
+        navigateAndWaitForCompletion(url,
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+        Assert.assertFalse(mCallback.onStartedCallback.isServedFromBackForwardCache());
+
+        String url2 = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url2);
+        Assert.assertFalse(mCallback.onStartedCallback.isServedFromBackForwardCache());
+
+        navigateAndWaitForCompletion(
+                url, () -> { activity.getTab().getNavigationController().goBack(); });
+        Assert.assertTrue(mCallback.onStartedCallback.isServedFromBackForwardCache());
+    }
+
+    @MinWebLayerVersion(90)
+    @Test
+    @SmallTest
+    public void testIsFormSubmission() throws Exception {
+        InstrumentationActivity activity =
+                mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
+        setNavigationCallback(activity);
+
+        // Touch the page; this should submit the form.
+        int currentCallCount = mCallback.onStartedCallback.getCallCount();
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+
+        mCallback.onStartedCallback.waitForCallback(currentCallCount);
+        assertEquals(true, mCallback.onStartedCallback.isFormSubmission());
+    }
+
+    @MinWebLayerVersion(90)
+    @Test
+    @SmallTest
+    public void testGetReferrer() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+        String referrer = "http://foo.com/";
+        NavigationCallback navigationCallback = new NavigationCallback() {
+            @Override
+            public void onNavigationStarted(Navigation navigation) {
+                try {
+                    navigation.setRequestHeader("Referer", referrer);
+                } catch (IllegalStateException e) {
+                }
+            }
+        };
+
+        registerNavigationCallback(navigationCallback);
+        int currentCallCount = mCallback.onCompletedCallback.getCallCount();
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url);
+        mCallback.onCompletedCallback.waitForCallback(currentCallCount);
+        assertEquals(referrer, mCallback.onCompletedCallback.getReferrer().toString());
+    }
+
+    /* Disable BackForwardCacheMemoryControls to allow BackForwardCache for all devices regardless
+     * of their memory. */
+    @MinWebLayerVersion(90)
+    @Test
+    @SmallTest
+    @CommandLineFlags.
+    Add({"enable-features=BackForwardCache", "disable-features=BackForwardCacheMemoryControls"})
+    public void testPageApi() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+
+        String url1 = mActivityTestRule.getTestServer().getURL("/echo");
+        navigateAndWaitForCompletion(url1,
+                () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url1)); });
+        Page page1 = mCallback.onCompletedCallback.getPage();
+
+        // Ensure the second page doesn't go into bfcache so that we can observe its Page object
+        // being destroyed.
+        List<Pair<String, String>> headers =
+                Collections.singletonList(Pair.create("Cache-Control", "no-store"));
+        String url2 = testServer.setResponse("/ok.html", "<html>ok</html>", headers);
+        mActivityTestRule.navigateAndWait(url2);
+        Page page2 = mCallback.onCompletedCallback.getPage();
+        assertNotEquals(page1, page2);
+
+        int curOnPageDestroyedCount = mCallback.onPageDestroyedCallback.getCallCount();
+
+        navigateAndWaitForCompletion(
+                url1, () -> { activity.getTab().getNavigationController().goBack(); });
+        Assert.assertTrue(mCallback.onCompletedCallback.isServedFromBackForwardCache());
+        Page page3 = mCallback.onCompletedCallback.getPage();
+        assertEquals(page1, page3);
+
+        mCallback.onPageDestroyedCallback.assertCalledWith(curOnPageDestroyedCount, page2);
+    }
+
+    @MinWebLayerVersion(91)
+    @Test
+    @SmallTest
+    public void testResponseHeaders() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+
+        int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
+
+        String url = mActivityTestRule.getTestServer().getURL("/echo");
+        mActivityTestRule.navigateAndWait(url);
+
+        mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, url);
+
+        Map<String, String> headers = mCallback.onCompletedCallback.getResponseHeaders();
+        assertEquals(headers.get("Content-Type"), "text/html");
+    }
+
+    @MinWebLayerVersion(92)
+    @Test
+    @SmallTest
+    public void testGetNavigationEntryOffset() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+
+        mActivityTestRule.navigateAndWait(URL2);
+        assertEquals(1, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        mActivityTestRule.navigateAndWait(URL3);
+        assertEquals(1, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        NavigationController navigationController =
+                runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
+
+        navigateAndWaitForCompletion(URL2, () -> navigationController.goBack());
+        assertEquals(-1, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        navigateAndWaitForCompletion(URL3, () -> navigationController.goForward());
+        assertEquals(1, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        navigateAndWaitForCompletion(URL3, () -> navigationController.reload());
+        assertEquals(0, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        navigateAndWaitForCompletion(URL1, () -> navigationController.goToIndex(0));
+        assertEquals(-2, mCallback.onCompletedCallback.getNavigationEntryOffset());
+
+        navigateAndWaitForCompletion(URL3, () -> navigationController.goToIndex(2));
+        assertEquals(2, mCallback.onCompletedCallback.getNavigationEntryOffset());
+    }
+
+    @MinWebLayerVersion(93)
+    @Test
+    @SmallTest
+    public void testOnPageLanguageDetermined() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        setNavigationCallback(activity);
+
+        int curLanguageDeterminedCount = mCallback.onPageLanguageDeterminedCallback.getCallCount();
+
+        // Navigate to a page in English.
+        String url = mActivityTestRule.getTestDataURL(ENGLISH_PAGE);
+        mActivityTestRule.navigateAndWait(url);
+
+        Page committedPage = mCallback.onCompletedCallback.getPage();
+        assertNotNull(committedPage);
+
+        // Verify that the language determined callback is fired as expected.
+        mCallback.onPageLanguageDeterminedCallback.waitForCallback(curLanguageDeterminedCount);
+
+        assertEquals(committedPage, mCallback.onPageLanguageDeterminedCallback.getPage());
+        assertEquals("en", mCallback.onPageLanguageDeterminedCallback.getLanguage());
+
+        // Now navigate to a page in French.
+        committedPage = null;
+        curLanguageDeterminedCount = mCallback.onPageLanguageDeterminedCallback.getCallCount();
+
+        url = mActivityTestRule.getTestDataURL(FRENCH_PAGE);
+        mActivityTestRule.navigateAndWait(url);
+
+        committedPage = mCallback.onCompletedCallback.getPage();
+        assertNotNull(committedPage);
+
+        // Verify that the language determined callback is fired as expected.
+        mCallback.onPageLanguageDeterminedCallback.waitForCallback(curLanguageDeterminedCount);
+
+        assertEquals(committedPage, mCallback.onPageLanguageDeterminedCallback.getPage());
+        assertEquals("fr", mCallback.onPageLanguageDeterminedCallback.getLanguage());
+    }
+
+    @MinWebLayerVersion(102)
+    @Test
+    @SmallTest
+    public void testWasFetchedFromCache() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
+        setNavigationCallback(activity);
+        String url = mActivityTestRule.getTestServer().getURL("/cachetime");
+
+        mActivityTestRule.navigateAndWait(url);
+        assertFalse(mCallback.onCompletedCallback.wasFetchedFromCache());
+
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestServer().getURL("/cachetime?foo"));
+        assertFalse(mCallback.onCompletedCallback.wasFetchedFromCache());
+
+        mActivityTestRule.navigateAndWait(url);
+        assertTrue(mCallback.onCompletedCallback.wasFetchedFromCache());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationWaiter.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationWaiter.java
new file mode 100644
index 0000000..afa1203
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationWaiter.java
@@ -0,0 +1,108 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.hamcrest.Matchers.lessThan;
+
+import android.net.Uri;
+
+import org.junit.Assert;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
+import org.chromium.weblayer.Tab;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper that blocks until a navigation completes.
+ */
+public class NavigationWaiter {
+    private String mUrl;
+    private Tab mTab;
+    private boolean mNavigationObserved;
+    /* True indicates that the expected navigation event is a failure. False indicates that the
+     * expected event is completion. */
+    private boolean mExpectFailure;
+    private boolean mDoneLoading;
+    private boolean mContentfulPaint;
+    private CallbackHelper mCallbackHelper = new CallbackHelper();
+
+    private NavigationCallback mNavigationCallback = new NavigationCallback() {
+        @Override
+        public void onNavigationCompleted(Navigation navigation) {
+            if (navigation.getUri().toString().equals(mUrl) && !mExpectFailure) {
+                Assert.assertThat(navigation.getHttpStatusCode(), lessThan(300));
+                mNavigationObserved = true;
+                checkComplete();
+            }
+        }
+
+        @Override
+        public void onNavigationFailed(Navigation navigation) {
+            if (navigation.getUri().toString().equals(mUrl) && mExpectFailure) {
+                mNavigationObserved = true;
+                checkComplete();
+            }
+        }
+
+        @Override
+        public void onLoadStateChanged(boolean isLoading, boolean shouldShowLoadingUi) {
+            mDoneLoading = !isLoading;
+            checkComplete();
+        }
+
+        @Override
+        public void onFirstContentfulPaint() {
+            mContentfulPaint = true;
+            checkComplete();
+        }
+    };
+
+    // |waitForPaint| should generally be set to true, unless there is a specific reason for
+    // onFirstContentfulPaint to not fire.
+    public NavigationWaiter(
+            String url, Tab controller, boolean expectFailure, boolean waitForPaint) {
+        mUrl = url;
+        mTab = controller;
+        if (!waitForPaint) mContentfulPaint = true;
+        mExpectFailure = expectFailure;
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTab.getNavigationController().registerNavigationCallback(mNavigationCallback);
+        });
+    }
+
+    /**
+     * Blocks until the navigation specified in the constructor completes.
+     *
+     * This also cleans up state, so it should only be called once per class instance.
+     */
+    public void waitForNavigation() {
+        try {
+            mCallbackHelper.waitForCallback(
+                    0, 1, CallbackHelper.WAIT_TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            throw new RuntimeException(e);
+        }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTab.getNavigationController().unregisterNavigationCallback(mNavigationCallback);
+        });
+    }
+
+    public void navigateAndWait() {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTab.getNavigationController().navigate(Uri.parse(mUrl)); });
+        waitForNavigation();
+    }
+
+    private void checkComplete() {
+        if (mNavigationObserved && mDoneLoading && mContentfulPaint) {
+            mCallbackHelper.notifyCalled();
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NetworkChangeNotifierTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NetworkChangeNotifierTest.java
new file mode 100644
index 0000000..b289391e
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NetworkChangeNotifierTest.java
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONException;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/** Tests that integration with NetworkStateNotifier works as expected. */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class NetworkChangeNotifierTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    @Feature({"WebLayer"})
+    public void testNetworkChangeNotifierAutoDetectRegistered()
+            throws RemoteException, JSONException {
+        // This creates an activity with WebLayer loaded, and causes WebLayer
+        // code to start listening to changes in network connectivity.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(activity.getApplicationContext());
+        Assert.assertTrue(testWebLayer.isNetworkChangeAutoDetectOn());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackImpl.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackImpl.java
new file mode 100644
index 0000000..9a0f216
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackImpl.java
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.weblayer.NewTabCallback;
+import org.chromium.weblayer.Tab;
+
+/**
+ * NewTabCallback test helper. Primarily used to wait for a new tab to be created.
+ */
+public class NewTabCallbackImpl extends NewTabCallback {
+    private final CallbackHelper mCallbackHelper = new CallbackHelper();
+
+    @Override
+    public void onNewTab(Tab tab, int mode) {
+        mCallbackHelper.notifyCalled();
+        tab.getBrowser().setActiveTab(tab);
+    }
+
+    public void waitForNewTab() {
+        try {
+            // waitForFirst() only handles a single call. If you need more convert from
+            // waitForFirst().
+            mCallbackHelper.waitForFirst();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackTest.java
new file mode 100644
index 0000000..5e63286
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NewTabCallbackTest.java
@@ -0,0 +1,95 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.NewTabCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that NewTabCallback methods are invoked as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class NewTabCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    public void testNewBrowser() {
+        String url = mActivityTestRule.getTestDataURL("new_browser.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        NewTabCallbackImpl callback = new NewTabCallbackImpl();
+        Tab firstTab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            Tab tab = mActivity.getBrowser().getActiveTab();
+            tab.setNewTabCallback(callback);
+            return tab;
+        });
+
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        callback.waitForNewTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertEquals(2, mActivity.getBrowser().getTabs().size());
+            Tab secondTab = mActivity.getBrowser().getActiveTab();
+            Assert.assertNotSame(firstTab, secondTab);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyTabInOnNewTab() throws Throwable {
+        String url = mActivityTestRule.getTestDataURL("new_browser.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = mActivity.getBrowser().getActiveTab();
+            tab.setNewTabCallback(new NewTabCallback() {
+                @Override
+                public void onNewTab(Tab newTab, int mode) {
+                    newTab.getBrowser().destroyTab(newTab);
+                    Assert.assertTrue(newTab.isDestroyed());
+                    Assert.assertEquals(1, mActivity.getBrowser().getTabs().size());
+                    Assert.assertFalse(mActivity.getBrowser().getActiveTab().isDestroyed());
+                    callbackHelper.notifyCalled();
+                }
+            });
+        });
+
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    public void testNewTabHasFocus() {
+        String url = mActivityTestRule.getTestDataURL("new_browser.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        NewTabCallbackImpl callback = new NewTabCallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().setNewTabCallback(callback); });
+
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        callback.waitForNewTab();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            return mActivityTestRule.executeScriptAndExtractBoolean("document.hasFocus()");
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java
new file mode 100644
index 0000000..62dd156
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+
+/**
+ * TabListCallback test helper. Primarily used to wait for a tab to be closed.
+ */
+public class OnTabRemovedTabListCallbackImpl extends TabListCallback {
+    private final CallbackHelper mCallbackHelper = new CallbackHelper();
+
+    @Override
+    public void onTabRemoved(Tab tab) {
+        mCallbackHelper.notifyCalled();
+    }
+
+    public void waitForCloseTab() {
+        try {
+            // waitForFirst() only handles a single call. If you need more convert from
+            // waitForFirst().
+            mCallbackHelper.waitForFirst();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean hasClosed() {
+        return mCallbackHelper.getCallCount() > 0;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PopupTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PopupTest.java
new file mode 100644
index 0000000..2bf0af2
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PopupTest.java
@@ -0,0 +1,107 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.NewTabCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests that popup blocking works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class PopupTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private Context mRemoteContext;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        Assert.assertNotNull(mActivity);
+
+        mRemoteContext = TestWebLayer.getRemoteContext(mActivity.getApplicationContext());
+    }
+
+    @Test
+    @SmallTest
+    public void testOpenPopupFromInfoBar() throws Exception {
+        NewTabCallbackImpl callback = new NewTabCallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().setNewTabCallback(callback); });
+
+        // Try to open a popup.
+        mActivityTestRule.executeScriptSync("window.open('about:blank')", true);
+
+        // Make sure the infobar shows up and the popup has not been opened.
+        String packageName =
+                TestWebLayer.getWebLayerContext(mActivity.getApplicationContext()).getPackageName();
+        int buttonId = ResourceUtil.getIdentifier(mRemoteContext, "id/button_primary", packageName);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(mActivity.findViewById(buttonId), Matchers.notNullValue());
+        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { Assert.assertEquals(mActivity.getBrowser().getTabs().size(), 1); });
+
+        // Click the button on the infobar to open the popup.
+        EventUtils.simulateTouchCenterOfView(mActivity.findViewById(buttonId));
+        callback.waitForNewTab();
+    }
+
+    @Test
+    @SmallTest
+    // This is a regression test for https://crbug.com/1142090 and verifies no crash when
+    // NewTabCallback.onNewTab() destroys the supplied tab.
+    public void testOpenPopupFromInfoBarAndNewTabCallbackDestroysTab() throws Exception {
+        CallbackHelper helper = new CallbackHelper();
+        NewTabCallback callback = new NewTabCallback() {
+            @Override
+            public void onNewTab(Tab tab, int mode) {
+                tab.getBrowser().destroyTab(tab);
+                helper.notifyCalled();
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().setNewTabCallback(callback); });
+
+        // Try to open a popup.
+        mActivityTestRule.executeScriptSync("window.open('about:blank')", true);
+
+        // Make sure the infobar shows up and the popup has not been opened.
+        String packageName =
+                TestWebLayer.getWebLayerContext(mActivity.getApplicationContext()).getPackageName();
+        int buttonId = ResourceUtil.getIdentifier(mRemoteContext, "id/button_primary", packageName);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(mActivity.findViewById(buttonId), Matchers.notNullValue());
+        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { Assert.assertEquals(mActivity.getBrowser().getTabs().size(), 1); });
+
+        // Click the button on the infobar to open the popup.
+        EventUtils.simulateTouchCenterOfView(mActivity.findViewById(buttonId));
+
+        // Wait for tab to be destroyed.
+        helper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java
new file mode 100644
index 0000000..dfdad866
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java
@@ -0,0 +1,69 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.net.test.util.WebServer;
+import org.chromium.weblayer.PrerenderController;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Tests verifying PrerenderController behavior. */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class PrerenderControllerTest {
+    private static final String DEFAULT_BODY = "<html><title>TestPage</title>Hello World!</html>";
+    private CallbackHelper mPrerenderedPageFetched;
+
+    private WebServer.RequestHandler mRequestHandler = new WebServer.RequestHandler() {
+        @Override
+        public void handleRequest(WebServer.HTTPRequest request, OutputStream stream) {
+            try {
+                if (request.getURI().contains("prerendered_page.html")) {
+                    TestThreadUtils.runOnUiThreadBlocking(
+                            () -> mPrerenderedPageFetched.notifyCalled());
+                }
+                WebServer.writeResponse(stream, WebServer.STATUS_OK, DEFAULT_BODY.getBytes());
+            } catch (IOException exception) {
+                Assert.fail(exception.getMessage()
+                        + " \n while handling request: " + request.toString());
+            }
+        }
+    };
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    @Feature({"WebLayer"})
+    public void testAddingPrerender() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        testServer.setRequestHandler(mRequestHandler);
+        mPrerenderedPageFetched = new CallbackHelper();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PrerenderController prerenderController =
+                    activity.getBrowser().getProfile().getPrerenderController();
+            prerenderController.schedulePrerender(
+                    Uri.parse(testServer.getResponseUrl("/prerendered_page.html")));
+        });
+        mPrerenderedPageFetched.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java
new file mode 100644
index 0000000..fa424b7
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java
@@ -0,0 +1,305 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+import android.webkit.ValueCallback;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.ApplicationTestUtils;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Callback;
+import org.chromium.weblayer.CookieManager;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.WebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Tests that Profile works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ProfileTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    public void testCreateAndGetAllProfiles() {
+        WebLayer weblayer = mActivityTestRule.getWebLayer();
+        {
+            // Start with empty profile.
+            Collection<Profile> profiles = getAllProfiles();
+            Assert.assertTrue(profiles.isEmpty());
+        }
+
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        Profile firstProfile = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getBrowser().getProfile());
+        {
+            // Launching an activity with a fragment creates one profile.
+            Collection<Profile> profiles = getAllProfiles();
+            Assert.assertEquals(1, profiles.size());
+            Assert.assertTrue(profiles.contains(firstProfile));
+        }
+
+        Profile secondProfile = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return weblayer.getProfile("second_test"); });
+
+        {
+            Collection<Profile> profiles = getAllProfiles();
+            Assert.assertEquals(2, profiles.size());
+            Assert.assertTrue(profiles.contains(firstProfile));
+            Assert.assertTrue(profiles.contains(secondProfile));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyAndDeleteDataFromDiskWhenInUse() {
+        WebLayer weblayer = mActivityTestRule.getWebLayer();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        final Profile profile = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getBrowser().getProfile());
+
+        try {
+            Callable<Void> c = () -> {
+                profile.destroyAndDeleteDataFromDisk(null);
+                return null;
+            };
+            TestThreadUtils.runOnUiThreadBlocking(c);
+            Assert.fail();
+        } catch (ExecutionException e) {
+            // Expected.
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyAndDeleteDataFromDiskSoonWhenInUse() throws Exception {
+        WebLayer weblayer = mActivityTestRule.getWebLayer();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        Profile profile =
+                TestThreadUtils.runOnUiThreadBlocking(() -> activity.getBrowser().getProfile());
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            profile.destroyAndDeleteDataFromDiskSoon(callbackHelper::notifyCalled);
+            FragmentManager fm = activity.getSupportFragmentManager();
+            fm.beginTransaction()
+                    .remove(fm.getFragments().get(0))
+                    .runOnCommit(callbackHelper::notifyCalled)
+                    .commit();
+        });
+        callbackHelper.waitForCallback(0, 2);
+        Collection<Profile> profiles = getAllProfiles();
+        Assert.assertFalse(profiles.contains(profile));
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyAndDeleteDataFromDisk() throws Exception {
+        doTestDestroyAndDeleteDataFromDiskIncognito("testDestroyAndDeleteDataFromDisk");
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyAndDeleteDataFromDiskIncognito() throws Exception {
+        doTestDestroyAndDeleteDataFromDiskIncognito(null);
+    }
+
+    private void doTestDestroyAndDeleteDataFromDiskIncognito(final String name) throws Exception {
+        WebLayer weblayer = mActivityTestRule.getWebLayer();
+        final Profile profile =
+                TestThreadUtils.runOnUiThreadBlockingNoException(() -> weblayer.getProfile(name));
+
+        {
+            Collection<Profile> profiles = getAllProfiles();
+            Assert.assertTrue(profiles.contains(profile));
+        }
+
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> profile.destroyAndDeleteDataFromDisk(callbackHelper::notifyCalled));
+        {
+            Collection<Profile> profiles = getAllProfiles();
+            Assert.assertFalse(profiles.contains(profile));
+        }
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "see crbug.com/1359894")
+    public void testEnumerateAllProfileNames() throws Exception {
+        final String profileName = "TestEnumerateAllProfileNames";
+        final InstrumentationActivity activity = mActivityTestRule.launchWithProfile(profileName);
+        final Profile profile = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getBrowser().getProfile());
+
+        Assert.assertTrue(Arrays.asList(enumerateAllProfileNames()).contains(profileName));
+
+        ApplicationTestUtils.finishActivity(activity);
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> profile.destroyAndDeleteDataFromDisk(callbackHelper::notifyCalled));
+        callbackHelper.waitForFirst();
+
+        Assert.assertFalse(Arrays.asList(enumerateAllProfileNames()).contains(profileName));
+    }
+
+    private Profile launchAndDestroyActivity(
+            String profileName, ValueCallback<InstrumentationActivity> callback) throws Exception {
+        final InstrumentationActivity activity = mActivityTestRule.launchWithProfile(profileName);
+        final Profile profile = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getBrowser().getProfile());
+
+        callback.onReceiveValue(activity);
+
+        ApplicationTestUtils.finishActivity(activity);
+        return profile;
+    }
+
+    private void destroyAndDeleteDataFromDisk(Profile profile) throws Exception {
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> profile.destroyAndDeleteDataFromDisk(callbackHelper::notifyCalled));
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "see crbug.com/1359894")
+    public void testReuseProfile() throws Exception {
+        final String profileName = "ReusedProfile";
+        final Uri uri = Uri.parse("https://foo.bar");
+        final String expectedCookie = "foo=bar";
+        {
+            // Create profile and activity and set a cookie.
+            launchAndDestroyActivity(profileName, (InstrumentationActivity activity) -> {
+                CookieManager cookieManager = TestThreadUtils.runOnUiThreadBlockingNoException(
+                        () -> { return activity.getBrowser().getProfile().getCookieManager(); });
+                try {
+                    boolean result =
+                            mActivityTestRule.setCookie(cookieManager, uri, expectedCookie);
+                    Assert.assertTrue(result);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+
+        {
+            // Without deleting proifle data, create profile and activity again, and check the
+            // cookie is there.
+            Profile profile =
+                    launchAndDestroyActivity(profileName, (InstrumentationActivity activity) -> {
+                        CookieManager cookieManager =
+                                TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+                                    return activity.getBrowser().getProfile().getCookieManager();
+                                });
+                        try {
+                            String cookie = mActivityTestRule.getCookie(cookieManager, uri);
+                            Assert.assertEquals(expectedCookie, cookie);
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    });
+
+            destroyAndDeleteDataFromDisk(profile);
+        }
+
+        {
+            // After deleting profile data, create profile and activity again, and check the cookie
+            // is deleted as well.
+            launchAndDestroyActivity(profileName, (InstrumentationActivity activity) -> {
+                CookieManager cookieManager = TestThreadUtils.runOnUiThreadBlockingNoException(
+                        () -> { return activity.getBrowser().getProfile().getCookieManager(); });
+                try {
+                    String cookie = mActivityTestRule.getCookie(cookieManager, uri);
+                    Assert.assertEquals("", cookie);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+    }
+
+    private String[] enumerateAllProfileNames() throws Exception {
+        final String[][] holder = new String[1][];
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Callback<String[]> callback = new Callback<String[]>() {
+                @Override
+                public void onResult(String[] names) {
+                    holder[0] = names;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            mActivityTestRule.getWebLayer().enumerateAllProfileNames(callback);
+        });
+        callbackHelper.waitForFirst();
+        return holder[0];
+    }
+
+    private static Collection<Profile> getAllProfiles() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> Profile.getAllProfiles());
+    }
+
+    @Test
+    @SmallTest
+    public void testMultipleIncognitoProfiles() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            WebLayer weblayer = mActivityTestRule.getWebLayer();
+            Profile profile = activity.getBrowser().getProfile();
+            Assert.assertFalse(profile.isIncognito());
+            final String incognitoProfileName1 = "incognito1";
+            Profile incognitoProfile1 = weblayer.getIncognitoProfile(incognitoProfileName1);
+            Assert.assertTrue(incognitoProfile1.isIncognito());
+            Assert.assertEquals(incognitoProfileName1, incognitoProfile1.getName());
+
+            final String incognitoProfileName2 = "incognito2";
+            Profile incognitoProfile2 = weblayer.getIncognitoProfile(incognitoProfileName2);
+            Assert.assertTrue(incognitoProfile2.isIncognito());
+            Assert.assertEquals(incognitoProfileName2, incognitoProfile2.getName());
+            Assert.assertNotEquals(incognitoProfile1, incognitoProfile2);
+
+            // Calling getIncognitoProfile() should return the same Profile.
+            Assert.assertEquals(
+                    incognitoProfile1, weblayer.getIncognitoProfile(incognitoProfileName1));
+            Assert.assertEquals(
+                    incognitoProfile2, weblayer.getIncognitoProfile(incognitoProfileName2));
+
+            // getAllProfiles() should return the incognito profiles.
+            Collection<Profile> allProfiles = Profile.getAllProfiles();
+            Assert.assertEquals(3, allProfiles.size());
+            Assert.assertTrue(allProfiles.contains(profile));
+            Assert.assertTrue(allProfiles.contains(incognitoProfile1));
+            Assert.assertTrue(allProfiles.contains(incognitoProfile2));
+        });
+
+        // Incognito profiles should not be returned from enumerateAllProfileNames().
+        String[] profileNames = enumerateAllProfileNames();
+        Assert.assertEquals(1, profileNames.length);
+        Assert.assertEquals(profileNames[0],
+                TestThreadUtils.runOnUiThreadBlocking(
+                        () -> activity.getBrowser().getProfile().getName()));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/RegisterExternalExperimentIdsTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/RegisterExternalExperimentIdsTest.java
new file mode 100644
index 0000000..69eea2a
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/RegisterExternalExperimentIdsTest.java
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Assertions for WebLayer.registerExternalExperimentIDs().
+ */
+// The flags are necessary for the following reasons:
+// host-resolver-rules: to make 'google.com' redirect to the port created by TestWebServer.
+// ignore-certificate-errors: TestWebServer doesn't have a real cert.
+@CommandLineFlags.Add({"host-resolver-rules='MAP * 127.0.0.1'", "ignore-certificate-errors"})
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class RegisterExternalExperimentIdsTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    /**
+     * This is an end-to-end test of registerExternalExperimentIDs. It also covers
+     * https://crbug.com/1147827.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(88) // Fix landed in 88.
+    public void testRegisterExternalExperimentIds() throws Exception {
+        mActivityTestRule.getTestServerRule().setServerUsesHttps(true);
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        TestWebServer testServer = TestWebServer.startSsl();
+        // Experiment ids are only added for certain domains, of which google is one.
+        testServer.setServerHost("google.com");
+        String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url);
+        String initialValue = testServer.getLastRequest("/ok.html").headerValue("X-Client-Data");
+        runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getWebLayer().registerExternalExperimentIDs(new int[] {1});
+        });
+
+        String url2 = testServer.setResponse("/ok2.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url2);
+        String secondValue = testServer.getLastRequest("/ok2.html").headerValue("X-Client-Data");
+        // The contents of the header are an encoded protobuf, which is a bit hard to verify in a
+        // test. The key things to assert is registering an id changes the value, and that it's not
+        // empty.
+        assertFalse(secondValue.isEmpty());
+        assertNotEquals(initialValue, secondValue);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceLoadingTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceLoadingTest.java
new file mode 100644
index 0000000..892abaf
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceLoadingTest.java
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/** Tests that resources are loaded correctly. */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public final class ResourceLoadingTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private Context mRemoteContext;
+    private String mPackageName;
+
+    @Before
+    public void setUp() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        mRemoteContext = TestWebLayer.getRemoteContext(activity.getApplicationContext());
+        mPackageName =
+                TestWebLayer.getWebLayerContext(activity.getApplicationContext()).getPackageName();
+    }
+
+    @Test
+    @SmallTest
+    public void testLayout() throws Exception {
+        Context themedContext =
+                new ContextThemeWrapper(mRemoteContext, getIdentifier("style/TestStyle"));
+        View view = LayoutInflater.from(themedContext)
+                            .inflate(getIdentifier("layout/test_layout"), null);
+        Assert.assertEquals(((ColorDrawable) view.getBackground()).getColor(), 0xff010101);
+    }
+
+    @Test
+    @SmallTest
+    public void testStyle() throws Exception {
+        Context themedContext =
+                new ContextThemeWrapper(mRemoteContext, getIdentifier("style/TestStyle"));
+        TypedValue tv = new TypedValue();
+        Assert.assertTrue(themedContext.getTheme().resolveAttribute(
+                getIdentifier("attr/testAttrColor"), tv, true));
+        Assert.assertEquals(tv.type, TypedValue.TYPE_INT_COLOR_RGB8);
+        Assert.assertEquals(tv.data, 0xff010101);
+    }
+
+    private int getIdentifier(String name) {
+        return ResourceUtil.getIdentifier(mRemoteContext, name, mPackageName);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceUtil.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceUtil.java
new file mode 100644
index 0000000..563fe6c
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ResourceUtil.java
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+
+/**
+ * Util class for dealing with resources.
+ */
+public class ResourceUtil {
+    public static final int REQUIRED_PACKAGE_IDENTIFIER = 36;
+
+    private ResourceUtil() {}
+
+    /** Gets the ID of a resource in a remote context. */
+    public static int getIdentifier(Context context, String name, String packageName) {
+        int id = context.getResources().getIdentifier(name, null, packageName);
+        // This was build with app_as_shared_lib, no need to modify package ID.
+        if ((id & 0xff000000) == 0x7f000000) {
+            return id;
+        }
+
+        // Force the returned ID to use our magic package ID.
+        id &= 0x00ffffff;
+        id |= (0x01000000 * REQUIRED_PACKAGE_IDENTIFIER);
+        return id;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ScrollOffsetCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ScrollOffsetCallbackTest.java
new file mode 100644
index 0000000..411f768
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ScrollOffsetCallbackTest.java
@@ -0,0 +1,96 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.ScrollOffsetCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test for ScrollOffsetCallback.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class ScrollOffsetCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "crbug.com/1190427")
+    public void testBasic() throws Throwable {
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+
+        // As registering for scroll offsets requires sending a message to the renderer, and
+        // requires viz to be in a particular state, ensuring scrolls will generate a message is a
+        // bit finicky. This tries to adjust the top-offset 10 times before giving up.
+        CallbackHelper callbackHelper = new CallbackHelper();
+        ScrollOffsetCallback scrollOffsetCallback = new ScrollOffsetCallback() {
+            @Override
+            public void onVerticalScrollOffsetChanged(int scrollOffset) {
+                callbackHelper.notifyCalled();
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().getActiveTab().registerScrollOffsetCallback(scrollOffsetCallback);
+        });
+
+        boolean gotInitialScroll = false;
+        for (int i = 1; i < 10 && !gotInitialScroll; ++i) {
+            mActivityTestRule.executeScriptSync("window.scroll(0, " + (i * 100) + ");", false);
+            try {
+                callbackHelper.waitForCallback(
+                        "Waiting for initial scroll", 0, 1, 100, TimeUnit.MILLISECONDS);
+                gotInitialScroll = true;
+            } catch (TimeoutException e) {
+            }
+        }
+        if (!gotInitialScroll) {
+            Assert.fail("Was unable to get initial scroll, failing");
+        }
+
+        // Scroll back to the origin.
+        CallbackHelper scrollBackCallbackHelper = new CallbackHelper();
+        ScrollOffsetCallback scrollBackOffsetCallback = new ScrollOffsetCallback() {
+            @Override
+            public void onVerticalScrollOffsetChanged(int scrollOffset) {
+                if (scrollOffset == 0) {
+                    scrollBackCallbackHelper.notifyCalled();
+                }
+            }
+        };
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().getActiveTab().registerScrollOffsetCallback(
+                    scrollBackOffsetCallback);
+        });
+        mActivityTestRule.executeScriptSync("window.scroll(0, 0);", false);
+        scrollBackCallbackHelper.waitForFirst();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getBrowser().getActiveTab().unregisterScrollOffsetCallback(
+                    scrollOffsetCallback);
+        });
+
+        // At this point scrollOffsetCallback is still registered. If this code were to remove the
+        // callback the renderer might temporarily disable scroll-offsets notification, which
+        // could potentially lead to raciness. In other words, best to leave it for future
+        // assertions.
+    }
+
+    // NOTE: if adding another test, you'll likely want to make testBasic() a setUp type function
+    // that is shared.
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SettingsActivityTestRule.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SettingsActivityTestRule.java
new file mode 100644
index 0000000..f57678d8
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SettingsActivityTestRule.java
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.SettingsActivity;
+import org.chromium.weblayer.WebLayer;
+
+/**
+ * ActivityTestRule for SettingsActivity.
+ *
+ * Test can use this ActivityTestRule to launch SettingsActivity.
+ */
+public class SettingsActivityTestRule extends WebLayerActivityTestRule<SettingsActivity> {
+    private Context mAppContext;
+
+    public SettingsActivityTestRule() {
+        super(SettingsActivity.class);
+    }
+
+    @Override
+    public void launchActivity(Intent settingsIntent) {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // We load WebLayer here so it gets initialized with the test/instrumentation Context,
+            // which has an in-memory SharedPreferences. Without this call, WebLayer gets
+            // initialized with the Application as its appContext, which breaks tests because a
+            // SharedPreferences xml file is persisted to disk.
+            WebLayer.loadSync(getContext());
+        });
+        super.launchActivity(settingsIntent);
+    }
+
+    public Context getContext() {
+        if (mAppContext == null) {
+            mAppContext = InstrumentationRegistry.getInstrumentation()
+                                  .getTargetContext()
+                                  .getApplicationContext();
+        }
+        return mAppContext;
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SiteSettingsTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SiteSettingsTest.java
new file mode 100644
index 0000000..4e3583b
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SiteSettingsTest.java
@@ -0,0 +1,140 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.SettingsTestUtils;
+import org.chromium.weblayer.SiteSettingsActivity;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.WebLayer;
+
+/**
+ * Tests the behavior of the Site Settings UI.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class SiteSettingsTest {
+    private static final String GOOGLE_URL = "https://www.google.com";
+    private static final String PROFILE_NAME = "DefaultProfile";
+
+    @Rule
+    public SettingsActivityTestRule mSettingsTestRule = new SettingsActivityTestRule();
+
+    @Test
+    @SmallTest
+    public void testSiteSettingsLaunches() throws InterruptedException {
+        mSettingsTestRule.launchActivity(
+                SiteSettingsActivity.createIntentForSiteSettingsCategoryList(
+                        mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false));
+
+        onView(withText("All sites")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testAllSitesLaunches() throws Exception {
+        mSettingsTestRule.launchActivity(
+                SiteSettingsActivity.createIntentForSiteSettingsCategoryList(
+                        mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false));
+        TestWebLayer.getTestWebLayer(mSettingsTestRule.getContext())
+                .grantLocationPermission(GOOGLE_URL);
+
+        onView(withText("All sites")).perform(click());
+
+        // Check that the google.com item is visible.
+        onView(withText(GOOGLE_URL)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testJavascriptExceptionPopupLaunches() throws InterruptedException {
+        mSettingsTestRule.launchActivity(
+                SiteSettingsActivity.createIntentForSiteSettingsCategoryList(
+                        mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false));
+
+        onView(withText("JavaScript")).perform(click());
+        onView(withText("Add site exception")).perform(click());
+
+        onView(withText("Block JavaScript for a specific site.")).check(matches(isDisplayed()));
+    }
+
+    @DisabledTest(message = "https://crbug.com/1174618")
+    @Test
+    @SmallTest
+    public void testAdBlockingSiteSettingPageLaunches() throws InterruptedException {
+        // The setting for ad blocking is below the fold on the main site settings page, and it's
+        // challenging to scroll to it. Launch directly to the category instead. See the discussion
+        // on https://chromium-review.googlesource.com/c/chromium/src/+/2673520 for further details.
+        // Note that this means that this test unfortunately doesn't verify that the Ads setting is
+        // actually present on the main site settings page.
+        mSettingsTestRule.launchActivity(
+                SettingsTestUtils.createIntentForSiteSettingsSingleCategory(
+                        mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false, "ads",
+                        "Ads"));
+
+        onView(withText("Block ads on sites that show intrusive or misleading ads"))
+                .perform(click());
+
+        onView(withText("Allowed")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSiteSoundToggle() throws InterruptedException {
+        mSettingsTestRule.launchActivity(SettingsTestUtils.createIntentForSiteSettingsSingleWebsite(
+                mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false, GOOGLE_URL));
+
+        onView(withText("Allowed")).check(matches(isDisplayed()));
+
+        onView(withText("Sound")).perform(click());
+
+        onView(withText("Blocked")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSiteClearPopupLaunches() throws InterruptedException {
+        mSettingsTestRule.launchActivity(SettingsTestUtils.createIntentForSiteSettingsSingleWebsite(
+                mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false, GOOGLE_URL));
+
+        onView(withText("Clear & reset")).perform(click());
+
+        onView(withText(containsString("Are you sure you want to clear all local data")))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSiteLocationAccess() throws InterruptedException, RemoteException {
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mSettingsTestRule.getContext());
+        testWebLayer.setSystemLocationSettingEnabled(true);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            WebLayer.loadSync(mSettingsTestRule.getContext()).getProfile(PROFILE_NAME);
+        });
+        testWebLayer.grantLocationPermission(GOOGLE_URL);
+        mSettingsTestRule.launchActivity(SettingsTestUtils.createIntentForSiteSettingsSingleWebsite(
+                mSettingsTestRule.getContext(), PROFILE_NAME, /*isIncognito=*/false, GOOGLE_URL));
+
+        onView(withText("Location")).perform(click());
+
+        onView(withText("Blocked")).check(matches(isDisplayed()));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
new file mode 100644
index 0000000..9f2444c
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/SmokeTest.java
@@ -0,0 +1,178 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.PixelCopy;
+
+import androidx.annotation.RequiresApi;
+import androidx.fragment.app.Fragment;
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.CriteriaNotSatisfiedException;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+
+/**
+ * Basic tests to make sure WebLayer works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class SmokeTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    // Should be used only for solid color pages, since the window is downsampled significantly.
+    @RequiresApi(Build.VERSION_CODES.O)
+    private int getWindowCenterPixelColor(Activity activity) {
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
+        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888, /*hasAlpha=*/true);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PixelCopy.request(activity.getWindow(), bitmap,
+                    (int copyResult) -> { latch.countDown(); }, new Handler(Looper.myLooper()));
+        });
+        latch.timedAwait();
+        int color = bitmap.getPixel(100, 100);
+        bitmap.recycle();
+        return color;
+    }
+
+    private void checkColorIsClose(int color1, int color2, int tolerance) {
+        Criteria.checkThat(
+                Math.abs(Color.alpha(color1) - Color.alpha(color2)), Matchers.lessThan(tolerance));
+        Criteria.checkThat(
+                Math.abs(Color.red(color1) - Color.red(color2)), Matchers.lessThan(tolerance));
+        Criteria.checkThat(
+                Math.abs(Color.green(color1) - Color.green(color2)), Matchers.lessThan(tolerance));
+        Criteria.checkThat(
+                Math.abs(Color.blue(color1) - Color.blue(color2)), Matchers.lessThan(tolerance));
+    }
+
+    @Test
+    @SmallTest
+    public void testActivityShouldNotLeak() {
+        ReferenceQueue<InstrumentationActivity> referenceQueue = new ReferenceQueue<>();
+        PhantomReference<InstrumentationActivity> reference;
+        {
+            InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+            // This installs a fullscreen callback, and is to ensure setting a fullscreen callback
+            // doesn't leak.
+            TestFullscreenCallback fullscreenCallback =
+                    new TestFullscreenCallback(mActivityTestRule);
+            mActivityTestRule.recreateActivity();
+            boolean destroyed =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.isDestroyed());
+            Assert.assertTrue(destroyed);
+
+            reference = new PhantomReference<>(activity, referenceQueue);
+        }
+
+        Runtime.getRuntime().gc();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Reference enqueuedReference = referenceQueue.poll();
+            if (enqueuedReference == null) {
+                Runtime.getRuntime().gc();
+                throw new CriteriaNotSatisfiedException("No enqueued reference");
+            }
+            Assert.assertEquals(reference, enqueuedReference);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testRecreateInstance() {
+        try {
+            InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+            });
+            mActivityTestRule.setRetainInstance(false);
+            Fragment firstFragment = mActivityTestRule.getFragment();
+
+            mActivityTestRule.recreateByRotatingToLandscape();
+            boolean destroyed =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.isDestroyed());
+            Assert.assertTrue(destroyed);
+
+            Fragment secondFragment = mActivityTestRule.getFragment();
+            Assert.assertNotSame(firstFragment, secondFragment);
+        } finally {
+            Activity activity = mActivityTestRule.getActivity();
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+            });
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRetainInstance() {
+        ReferenceQueue<InstrumentationActivity> referenceQueue = new ReferenceQueue<>();
+        PhantomReference<InstrumentationActivity> reference;
+        {
+            InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+            mActivityTestRule.setRetainInstance(true);
+            Fragment firstFragment = mActivityTestRule.getFragment();
+            mActivityTestRule.recreateActivity();
+            Fragment secondFragment = mActivityTestRule.getFragment();
+            Assert.assertEquals(firstFragment, secondFragment);
+
+            boolean destroyed =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.isDestroyed());
+            Assert.assertTrue(destroyed);
+            reference = new PhantomReference<>(activity, referenceQueue);
+        }
+
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Reference enqueuedReference = referenceQueue.poll();
+            if (enqueuedReference == null) {
+                Runtime.getRuntime().gc();
+                throw new CriteriaNotSatisfiedException("No enqueued reference");
+            }
+            Assert.assertEquals(reference, enqueuedReference);
+        });
+    }
+
+    // Verifies recreating the Activity destroys the original Fragment when using ViewModel.
+    @Test
+    @SmallTest
+    public void testRecreateActivityDestroysFragmentUseViewModel() throws Throwable {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_USE_VIEW_MODEL, true);
+        mActivityTestRule.launchShellWithUrl("about:blank", extras);
+        ReferenceQueue<Fragment> referenceQueue = new ReferenceQueue<>();
+        PhantomReference<Fragment> reference =
+                new PhantomReference<>(mActivityTestRule.getFragment(), referenceQueue);
+        mActivityTestRule.recreateActivity();
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Reference enqueuedReference = referenceQueue.poll();
+            if (enqueuedReference == null) {
+                Runtime.getRuntime().gc();
+                throw new CriteriaNotSatisfiedException("No enqueued reference");
+            }
+            Assert.assertEquals(reference, enqueuedReference);
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
new file mode 100644
index 0000000..6013659b
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
@@ -0,0 +1,468 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.content_public.browser.test.util.TestTouchUtils;
+import org.chromium.weblayer.ContextMenuParams;
+import org.chromium.weblayer.Profile;
+import org.chromium.weblayer.ScrollNotificationType;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests that TabCallback methods are invoked as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class TabCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private static class Callback extends TabCallback {
+        public static class TabCallbackValueRecorder {
+            private List<String> mObservedValues =
+                    Collections.synchronizedList(new ArrayList<String>());
+
+            public void recordValue(String parameter) {
+                mObservedValues.add(parameter);
+            }
+
+            public List<String> getObservedValues() {
+                return mObservedValues;
+            }
+
+            public void waitUntilValueObserved(String expectation) {
+                CriteriaHelper.pollInstrumentationThread(
+                        () -> Criteria.checkThat(expectation, Matchers.isIn(mObservedValues)));
+            }
+        }
+
+        public TabCallbackValueRecorder visibleUriChangedCallback = new TabCallbackValueRecorder();
+
+        @Override
+        public void onVisibleUriChanged(Uri uri) {
+            visibleUriChangedCallback.recordValue(uri.toString());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadEvents() {
+        String startupUrl = "about:blank";
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        Callback callback = new Callback();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getTab().registerTabCallback(callback); });
+
+        String url = "data:text,foo";
+        mActivityTestRule.navigateAndWait(url);
+
+        /* Verify that the visible URL changes to the target. */
+        callback.visibleUriChangedCallback.waitUntilValueObserved(url);
+    }
+
+    private ContextMenuParams runContextMenuTest(String file) throws TimeoutException {
+        String pageUrl = mActivityTestRule.getTestDataURL(file);
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(pageUrl);
+
+        ContextMenuParams params[] = new ContextMenuParams[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void showContextMenu(ContextMenuParams param) {
+                    params[0] = param;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        TestTouchUtils.longClickView(
+                InstrumentationRegistry.getInstrumentation(), activity.getWindow().getDecorView());
+        callbackHelper.waitForFirst();
+        Assert.assertEquals(Uri.parse(pageUrl), params[0].pageUri);
+        return params[0];
+    }
+
+    @Test
+    @SmallTest
+    public void testShowContextMenu() throws TimeoutException {
+        ContextMenuParams params = runContextMenuTest("download.html");
+        Assert.assertEquals(
+                Uri.parse(mActivityTestRule.getTestDataURL("lorem_ipsum.txt")), params.linkUri);
+        Assert.assertEquals("anchor text", params.linkText);
+    }
+
+    @Test
+    @SmallTest
+    public void testShowContextMenuImg() throws TimeoutException {
+        ContextMenuParams params = runContextMenuTest("img.html");
+        Assert.assertEquals(
+                Uri.parse(mActivityTestRule.getTestDataURL("favicon.png")), params.srcUri);
+        Assert.assertEquals("alt_text", params.titleOrAltText);
+    }
+
+    private File setTempDownloadDir() {
+        // Don't fill up the default download directory on the device.
+        File tempDownloadDirectory = new File(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir()
+                + "/weblayer/Downloads");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = mActivityTestRule.getActivity().getBrowser().getProfile();
+            profile.setDownloadDirectory(tempDownloadDirectory);
+        });
+        return tempDownloadDirectory;
+    }
+
+    private void waitForFileExist(File filePath, String fileName) {
+        File file = new File(filePath, fileName);
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat("Invalid file existence state for: " + fileName, file.exists(),
+                    Matchers.is(true));
+        });
+        file.delete();
+    }
+
+    @Test
+    @SmallTest
+    @DisableIf.Build(supported_abis_includes = "x86", sdk_is_greater_than = Build.VERSION_CODES.P,
+            message = "https://crbug.com/1201813")
+    @DisableIf.Build(supported_abis_includes = "x86_64",
+            sdk_is_greater_than = Build.VERSION_CODES.R, message = "https://crbug.com/1201813")
+    public void
+    testDownloadFromContextMenu() throws TimeoutException {
+        ContextMenuParams params = runContextMenuTest("download.html");
+        ;
+        Assert.assertFalse(params.isImage);
+        Assert.assertFalse(params.isVideo);
+        Assert.assertTrue(params.canDownload);
+
+        File tempDownloadDirectory = setTempDownloadDir();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivityTestRule.getActivity().getTab().download(params); });
+        waitForFileExist(tempDownloadDirectory, "lorem_ipsum.txt");
+    }
+
+    @Test
+    @SmallTest
+    @DisableIf.Build(supported_abis_includes = "x86", sdk_is_greater_than = Build.VERSION_CODES.P,
+            message = "https://crbug.com/1201813")
+    @DisableIf.Build(supported_abis_includes = "x86_64",
+            sdk_is_greater_than = Build.VERSION_CODES.R, message = "https://crbug.com/1201813")
+    public void
+    testDownloadFromContextMenuImg() throws TimeoutException {
+        ContextMenuParams params = runContextMenuTest("img.html");
+        ;
+        Assert.assertTrue(params.isImage);
+        Assert.assertFalse(params.isVideo);
+        Assert.assertTrue(params.canDownload);
+
+        File tempDownloadDirectory = setTempDownloadDir();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivityTestRule.getActivity().getTab().download(params); });
+        waitForFileExist(tempDownloadDirectory, "favicon.png");
+    }
+
+    @Test
+    @SmallTest
+    public void testTabModalOverlay() throws TimeoutException {
+        String pageUrl = mActivityTestRule.getTestDataURL("alert.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(pageUrl);
+        Assert.assertNotNull(activity);
+
+        Boolean isTabModalShowingResult[] = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTabModalStateChanged(boolean isTabModalShowing) {
+                    isTabModalShowingResult[0] = isTabModalShowing;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        int callCount = callbackHelper.getCallCount();
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(true, isTabModalShowingResult[0]);
+
+        callCount = callbackHelper.getCallCount();
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getTab().dismissTransientUi()));
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(false, isTabModalShowingResult[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testDismissTransientUi() throws TimeoutException {
+        String pageUrl = mActivityTestRule.getTestDataURL("alert.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(pageUrl);
+        Assert.assertNotNull(activity);
+
+        Boolean isTabModalShowingResult[] = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTabModalStateChanged(boolean isTabModalShowing) {
+                    isTabModalShowingResult[0] = isTabModalShowing;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        int callCount = callbackHelper.getCallCount();
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(true, isTabModalShowingResult[0]);
+
+        callCount = callbackHelper.getCallCount();
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getTab().dismissTransientUi()));
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(false, isTabModalShowingResult[0]);
+
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> activity.getTab().dismissTransientUi()));
+    }
+
+    @Test
+    @SmallTest
+    public void testTabModalOverlayOnBackgroundTab() throws TimeoutException {
+        // Create a tab.
+        String url = mActivityTestRule.getTestDataURL("new_browser.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(activity);
+        NewTabCallbackImpl callback = new NewTabCallbackImpl();
+        Tab firstTab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            Tab tab = activity.getBrowser().getActiveTab();
+            tab.setNewTabCallback(callback);
+            return tab;
+        });
+
+        // Tapping it creates a second tab, which is active.
+        EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
+        callback.waitForNewTab();
+
+        Tab secondTab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            Assert.assertEquals(2, activity.getBrowser().getTabs().size());
+            return activity.getBrowser().getActiveTab();
+        });
+        Assert.assertNotSame(firstTab, secondTab);
+
+        // Track tab modal updates.
+        Boolean isTabModalShowingResult[] = new Boolean[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        int callCount = callbackHelper.getCallCount();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            firstTab.registerTabCallback(new TabCallback() {
+                @Override
+                public void onTabModalStateChanged(boolean isTabModalShowing) {
+                    isTabModalShowingResult[0] = isTabModalShowing;
+                    callbackHelper.notifyCalled();
+                }
+            });
+        });
+
+        // Create an alert from the background tab. It shouldn't display. There's no way to
+        // consistently verify that nothing happens, but the script execution should finish, which
+        // is not the case for dialogs that show on an active tab until they're dismissed.
+        mActivityTestRule.executeScriptSync("window.alert('foo');", true, firstTab);
+        Assert.assertEquals(0, callbackHelper.getCallCount());
+
+        // When that tab becomes active again, the alert should show.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getBrowser().setActiveTab(firstTab); });
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(true, isTabModalShowingResult[0]);
+
+        // Switch away from the tab again; the alert should be hidden.
+        callCount = callbackHelper.getCallCount();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getBrowser().setActiveTab(secondTab); });
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(false, isTabModalShowingResult[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testOnTitleUpdated() throws TimeoutException {
+        String startupUrl = "about:blank";
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        String titles[] = new String[1];
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTitleUpdated(String title) {
+                    titles[0] = title;
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.navigateAndWait(url);
+        // Use polling because title is allowed to go through multiple transitions.
+        CriteriaHelper.pollUiThread(() -> Criteria.checkThat(titles[0], Matchers.is("OK")));
+
+        url = mActivityTestRule.getTestDataURL("shakespeare.html");
+        mActivityTestRule.navigateAndWait(url);
+        CriteriaHelper.pollUiThread(
+                () -> Criteria.checkThat(titles[0], Matchers.endsWith("shakespeare.html")));
+
+        mActivityTestRule.executeScriptSync("document.title = \"foobar\";", false);
+        Assert.assertEquals("foobar", titles[0]);
+        CriteriaHelper.pollUiThread(() -> Criteria.checkThat(titles[0], Matchers.is("foobar")));
+    }
+
+    @Test
+    @SmallTest
+    public void testOnBackgroundColorChanged() throws TimeoutException {
+        String startupUrl = "about:blank";
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        Integer backgroundColors[] = new Integer[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onBackgroundColorChanged(int color) {
+                    backgroundColors[0] = color;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        mActivityTestRule.executeScriptSync(
+                "document.body.style.backgroundColor = \"rgb(255, 0, 0)\"",
+                /*useSeparateIsolate=*/false);
+
+        callbackHelper.waitForFirst();
+        Assert.assertEquals(0xffff0000, (int) backgroundColors[0]);
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "crbug.com/1339982")
+    public void testScrollNotificationDirectionChange() throws TimeoutException {
+        final String url = mActivityTestRule.getTestDataURL("tall_page.html");
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
+
+        Integer notificationTypes[] = new Integer[1];
+        Float scrollRatio[] = new Float[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onScrollNotification(
+                        @ScrollNotificationType int notificationType, float currentScrollRatio) {
+                    notificationTypes[0] = notificationType;
+                    scrollRatio[0] = currentScrollRatio;
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        // Scroll to bottom of page.
+        int callCount = callbackHelper.getCallCount();
+        mActivityTestRule.executeScriptSync("window.scroll(0, 5000)",
+                /*useSeparateIsolate=*/false);
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(
+                ScrollNotificationType.DIRECTION_CHANGED_DOWN, (int) notificationTypes[0]);
+        Assert.assertTrue(scrollRatio[0] > 0.5);
+
+        // Scroll to top of page.
+        callCount = callbackHelper.getCallCount();
+        mActivityTestRule.executeScriptSync("window.scroll(0, 0)",
+                /*useSeparateIsolate=*/false);
+        callbackHelper.waitForCallback(callCount);
+        Assert.assertEquals(
+                ScrollNotificationType.DIRECTION_CHANGED_UP, (int) notificationTypes[0]);
+        Assert.assertTrue(scrollRatio[0] < 0.5);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    public void testOnVerticalOverscroll() throws TimeoutException {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+        float overscrollY[] = new float[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onVerticalOverscroll(float accumulatedOverscrollY) {
+                    overscrollY[0] = accumulatedOverscrollY;
+                    callbackHelper.notifyCalled();
+                    tab.unregisterTabCallback(this);
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        View decorView[] = new View[1];
+        int dimension[] = new int[2];
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            decorView[0] = activity.getWindow().getDecorView();
+            dimension[0] = decorView[0].getWidth();
+            dimension[1] = decorView[0].getHeight();
+        });
+
+        int x = dimension[0] / 2;
+        int fromY = dimension[1] / 3;
+        int toY = dimension[1] / 3 * 2;
+
+        TestTouchUtils.dragCompleteView(InstrumentationRegistry.getInstrumentation(), decorView[0],
+                /*fromX=*/x, /*toX=*/x, fromY, toY, /*stepCount=*/10);
+        callbackHelper.waitForFirst();
+        Assert.assertThat(overscrollY[0], Matchers.lessThan(0f));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabListCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabListCallbackTest.java
new file mode 100644
index 0000000..329c019
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabListCallbackTest.java
@@ -0,0 +1,220 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests that NewTabCallback methods are invoked as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class TabListCallbackTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private Tab mFirstTab;
+    private Tab mSecondTab;
+
+    private static class TabListCallbackImpl extends TabListCallback {
+        public static final String ADDED = "added";
+        public static final String ACTIVE = "active";
+        public static final String REMOVED = "removed";
+        public static final String WILL_DESTROY = "willdestroy";
+
+        private List<String> mObservedValues =
+                Collections.synchronizedList(new ArrayList<String>());
+
+        @Override
+        public void onActiveTabChanged(Tab activeTab) {
+            recordValue(ACTIVE);
+        }
+
+        @Override
+        public void onTabAdded(Tab tab) {
+            recordValue(ADDED);
+        }
+
+        @Override
+        public void onTabRemoved(Tab tab) {
+            recordValue(REMOVED);
+        }
+
+        @Override
+        public void onWillDestroyBrowserAndAllTabs() {
+            recordValue(WILL_DESTROY);
+        }
+
+        private void recordValue(String parameter) {
+            mObservedValues.add(parameter);
+        }
+
+        public List<String> getObservedValues() {
+            return mObservedValues;
+        }
+    }
+
+    protected void initialize(String testDataFile) {
+        String url = mActivityTestRule.getTestDataURL(testDataFile);
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        NewTabCallbackImpl callback = new NewTabCallbackImpl();
+        mFirstTab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            Tab tab = mActivity.getBrowser().getActiveTab();
+            tab.setNewTabCallback(callback);
+            return tab;
+        });
+
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        callback.waitForNewTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertEquals(2, mActivity.getBrowser().getTabs().size());
+            mSecondTab = mActivity.getBrowser().getActiveTab();
+            Assert.assertNotSame(mFirstTab, mSecondTab);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testActiveTabChanged() {
+        initialize("new_browser.html");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabListCallbackImpl callback = new TabListCallbackImpl();
+            mActivity.getBrowser().registerTabListCallback(callback);
+            mActivity.getBrowser().setActiveTab(mFirstTab);
+            Assert.assertTrue(callback.getObservedValues().contains(TabListCallbackImpl.ACTIVE));
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testMoveToDifferentFragment() {
+        initialize("new_browser.html");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser2 = Browser.fromFragment(mActivity.createBrowserFragment(0));
+            Browser browser1 = mActivity.getBrowser();
+            TabListCallbackImpl callback1 = new TabListCallbackImpl();
+            browser1.registerTabListCallback(callback1);
+
+            TabListCallbackImpl callback2 = new TabListCallbackImpl();
+            browser2.registerTabListCallback(callback2);
+
+            // Move the active tab from browser1 to browser2.
+            Tab tabToMove = browser1.getActiveTab();
+            browser2.addTab(tabToMove);
+            // This should notify callback1 the active tab changed and a tab was removed.
+            int browser1ActiveIndex =
+                    callback1.getObservedValues().indexOf(TabListCallbackImpl.ACTIVE);
+            Assert.assertNotSame(-1, browser1ActiveIndex);
+            int browser1RemoveIndex =
+                    callback1.getObservedValues().indexOf(TabListCallbackImpl.REMOVED);
+            Assert.assertNotSame(-1, browser1RemoveIndex);
+            Assert.assertTrue(browser1ActiveIndex < browser1RemoveIndex);
+            Assert.assertSame(null, browser1.getActiveTab());
+            Assert.assertEquals(1, browser1.getTabs().size());
+            Assert.assertFalse(browser1.getTabs().contains(tabToMove));
+
+            // callback2 should be notified of the insert.
+            Assert.assertTrue(callback2.getObservedValues().contains(TabListCallbackImpl.ADDED));
+            Assert.assertEquals(2, browser2.getTabs().size());
+            Assert.assertTrue(browser2.getTabs().contains(tabToMove));
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroyTab() {
+        initialize("new_browser.html");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabListCallbackImpl callback = new TabListCallbackImpl();
+            Browser browser = mActivity.getBrowser();
+            browser.registerTabListCallback(callback);
+            browser.destroyTab(mActivity.getBrowser().getActiveTab());
+            Assert.assertTrue(callback.getObservedValues().contains(TabListCallbackImpl.ACTIVE));
+            Assert.assertTrue(callback.getObservedValues().contains(TabListCallbackImpl.REMOVED));
+            Assert.assertEquals(1, browser.getTabs().size());
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testCallbackInvokedWhenTabClosedViaWebContents() {
+        initialize("new_tab_then_close.html");
+
+        OnTabRemovedTabListCallbackImpl closeTabCallback = new OnTabRemovedTabListCallbackImpl();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().registerTabListCallback(closeTabCallback);
+            // Switch to the first tab so clicking closes |secondTab|.
+            mSecondTab.getBrowser().setActiveTab(mFirstTab);
+        });
+
+        // Clicking on the tab again to callback to close the tab.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        closeTabCallback.waitForCloseTab();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnTabRemoved() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            browser.registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onTabRemoved(Tab tab) {
+                    // |tab| should not be destroyed at this point. getGuid() is a good proxy
+                    // for verifying the tab hasn't been destroyed.
+                    tab.getGuid();
+                    callbackHelper.notifyCalled();
+                }
+            });
+            mActivity.getBrowser().destroyTab(mActivity.getBrowser().createTab());
+        });
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnWillDestroyBrowserAndAllTabs() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TabListCallbackImpl tabListCallback = new TabListCallbackImpl();
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().registerTabListCallback(tabListCallback);
+            mActivity.getBrowser().registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onWillDestroyBrowserAndAllTabs() {
+                    callbackHelper.notifyCalled();
+                }
+            });
+            mActivity.destroyFragment();
+        });
+        callbackHelper.waitForFirst();
+        Assert.assertEquals(1, tabListCallback.getObservedValues().size());
+        Assert.assertTrue(
+                tabListCallback.getObservedValues().contains(TabListCallbackImpl.WILL_DESTROY));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabPrivateTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabPrivateTest.java
new file mode 100644
index 0000000..cfd85ae
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabPrivateTest.java
@@ -0,0 +1,118 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Navigation;
+import org.chromium.weblayer.NavigationCallback;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabCallback;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tab tests that need to use WebLayerPrivate.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class TabPrivateTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private TestWebLayer getTestWebLayer() {
+        return TestWebLayer.getTestWebLayer(mActivityTestRule.getContextForWebLayer());
+    }
+
+    @Test
+    @SmallTest
+    public void testCreateTabWithAccessibilityEnabledCrashTest() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                getTestWebLayer().setAccessibilityEnabled(true);
+            } catch (RemoteException e) {
+                Assert.fail("Unable to enable accessibility");
+            }
+            activity.getBrowser().createTab();
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testAutoReloadOnBackgroundCrash() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        activity.setIgnoreRendererCrashes();
+
+        CallbackHelper renderProcessGoneHelper = new CallbackHelper();
+        final Tab crashedTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = activity.getBrowser();
+            Tab originalTab = browser.getActiveTab();
+            originalTab.registerTabCallback(new TabCallback() {
+                @Override
+                public void onRenderProcessGone() {
+                    renderProcessGoneHelper.notifyCalled();
+                }
+            });
+
+            // Place a different tab in the foreground.
+            browser.setActiveTab(browser.createTab());
+
+            return originalTab;
+        });
+
+        // Crash the background tab.
+        getTestWebLayer().crashTab(crashedTab);
+        renderProcessGoneHelper.waitForFirst();
+
+        // Expect a navigation to occur when the crashed background tab is brought to the front.
+        // See logic in content's navigation_controller_impl.cc for why this is not classified as a
+        // reload.
+        CallbackHelper navigationCompletedHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            crashedTab.getNavigationController().registerNavigationCallback(
+                    new NavigationCallback() {
+                        @Override
+                        public void onNavigationCompleted(Navigation navigation) {
+                            navigationCompletedHelper.notifyCalled();
+                        }
+                    });
+
+            activity.getBrowser().setActiveTab(crashedTab);
+        });
+        navigationCompletedHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnRenderProcessGone() throws Exception {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        CallbackHelper callbackHelper = new CallbackHelper();
+        Tab tabToCrash = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            activity.setIgnoreRendererCrashes();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onRenderProcessGone() {
+                    callbackHelper.notifyCalled();
+                }
+            };
+            tab.registerTabCallback(callback);
+            return tab;
+        });
+        getTestWebLayer().crashTab(tabToCrash);
+        callbackHelper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabTest.java
new file mode 100644
index 0000000..e3116001
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabTest.java
@@ -0,0 +1,382 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.ActionModeCallback;
+import org.chromium.weblayer.ActionModeItemType;
+import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TabListCallback;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests for Tab.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class TabTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+
+    @Test
+    @SmallTest
+    public void testBeforeUnload() {
+        String url = mActivityTestRule.getTestDataURL("before_unload.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        Assert.assertTrue(mActivity.hasWindowFocus());
+
+        // Touch the view so that beforeunload will be respected (if the user doesn't interact with
+        // the tab, it's ignored).
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        // Round trip through the renderer to make sure te above touch is handled before we call
+        // dispatchBeforeUnloadAndClose().
+        mActivityTestRule.executeScriptSync("var x = 1", true);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().dispatchBeforeUnloadAndClose(); });
+
+        // Wait till the main window loses focus due to the app modal beforeunload dialog.
+        BoundedCountDownLatch noFocusLatch = new BoundedCountDownLatch(1);
+        BoundedCountDownLatch hasFocusLatch = new BoundedCountDownLatch(1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getWindow()
+                    .getDecorView()
+                    .getViewTreeObserver()
+                    .addOnWindowFocusChangeListener((boolean hasFocus) -> {
+                        (hasFocus ? hasFocusLatch : noFocusLatch).countDown();
+                    });
+        });
+        noFocusLatch.timedAwait();
+
+        // Verify closing the tab works still while beforeunload is showing (no crash).
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().destroyTab(mActivity.getBrowser().getActiveTab());
+        });
+
+        // Focus returns to the main window because the dialog is dismissed when the tab is
+        // destroyed.
+        hasFocusLatch.timedAwait();
+    }
+
+    @Test
+    @SmallTest
+    public void testBeforeUnloadNoHandler() {
+        String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        OnTabRemovedTabListCallbackImpl callback = new OnTabRemovedTabListCallbackImpl();
+        // Verify that calling dispatchBeforeUnloadAndClose will close the tab asynchronously when
+        // there is no beforeunload handler.
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            mActivity.getBrowser().registerTabListCallback(callback);
+            Tab tab = mActivity.getBrowser().getActiveTab();
+            tab.dispatchBeforeUnloadAndClose();
+            return callback.hasClosed();
+        }));
+
+        callback.waitForCloseTab();
+    }
+
+    @Test
+    @SmallTest
+    public void testBeforeUnloadNoInteraction() {
+        String url = mActivityTestRule.getTestDataURL("before_unload.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        OnTabRemovedTabListCallbackImpl callback = new OnTabRemovedTabListCallbackImpl();
+        // Verify that beforeunload is not run when there's no user action.
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            mActivity.getBrowser().registerTabListCallback(callback);
+            Tab tab = mActivity.getBrowser().getActiveTab();
+            tab.dispatchBeforeUnloadAndClose();
+            return callback.hasClosed();
+        }));
+
+        callback.waitForCloseTab();
+    }
+
+    private Bitmap captureScreenShot(float scale) throws TimeoutException {
+        Bitmap[] bitmapHolder = new Bitmap[1];
+        int[] errorCodeHolder = new int[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = mActivity.getTab();
+            tab.captureScreenShot(scale, (Bitmap bitmap, int errorCode) -> {
+                errorCodeHolder[0] = errorCode;
+                bitmapHolder[0] = bitmap;
+                callbackHelper.notifyCalled();
+            });
+        });
+        callbackHelper.waitForFirst();
+        Assert.assertNotNull(bitmapHolder[0]);
+        Assert.assertEquals(0, errorCodeHolder[0]);
+        return bitmapHolder[0];
+    }
+
+    private void checkQuadrantColors(Bitmap bitmap) {
+        int quarterWidth = bitmap.getWidth() / 4;
+        int quarterHeight = bitmap.getHeight() / 4;
+        Assert.assertEquals(Color.rgb(255, 0, 0), bitmap.getPixel(quarterWidth, quarterHeight));
+        Assert.assertEquals(Color.rgb(0, 255, 0), bitmap.getPixel(quarterWidth * 3, quarterHeight));
+        Assert.assertEquals(Color.rgb(0, 0, 255), bitmap.getPixel(quarterWidth, quarterHeight * 3));
+        Assert.assertEquals(
+                Color.rgb(128, 128, 128), bitmap.getPixel(quarterWidth * 3, quarterHeight * 3));
+    }
+
+    @Test
+    @SmallTest
+    public void testCaptureScreenShot() throws TimeoutException {
+        String url = mActivityTestRule.getTestDataURL("quadrant_colors.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+
+        Bitmap bitmap = captureScreenShot(1.f);
+        checkQuadrantColors(bitmap);
+        Bitmap halfBitmap = captureScreenShot(0.5f);
+        checkQuadrantColors(bitmap);
+
+        final int allowedError = 10;
+        Assert.assertTrue(Math.abs(bitmap.getWidth() / 2 - halfBitmap.getWidth()) < allowedError);
+        Assert.assertTrue(Math.abs(bitmap.getHeight() / 2 - halfBitmap.getHeight()) < allowedError);
+    }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    @DisabledTest(message = "https://crbug.com/1319845")
+    public void testCaptureScreenShotAfterResize() throws TimeoutException, ExecutionException {
+        String url = mActivityTestRule.getTestDataURL("quadrant_colors.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+
+        int newHeight = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            View view = mActivity.getFragment().getView();
+            int height = view.getHeight() + 10;
+            LinearLayout.LayoutParams params =
+                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
+            view.setLayoutParams(params);
+            view.requestLayout();
+            return height;
+        });
+
+        CriteriaHelper.pollUiThread(() -> {
+            View view = mActivity.getFragment().getView();
+            int height = view.getHeight();
+            Criteria.checkThat(height, Matchers.is(newHeight));
+        });
+
+        Bitmap bitmap = captureScreenShot(1.f);
+        checkQuadrantColors(bitmap);
+    }
+
+    @Test
+    @SmallTest
+    public void testCaptureScreenShotDoesNotHang() throws TimeoutException {
+        String startupUrl = "about:blank";
+        mActivity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = mActivity.getTab();
+            tab.captureScreenShot(1.f, (Bitmap bitmap, int errorCode) -> {
+                // Failure is ok here, so not checking |bitmap| or |errorCode|.
+                callbackHelper.notifyCalled();
+            });
+            mActivity.destroyFragment();
+        });
+        callbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetData() {
+        String startupUrl = "about:blank";
+        mActivity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        Map<String, String> data = new HashMap<>();
+        data.put("foo", "bar");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = mActivity.getTab();
+            tab.setData(data);
+            Assert.assertEquals(data.get("foo"), tab.getData().get("foo"));
+
+            tab.setData(new HashMap<>());
+            Assert.assertTrue(tab.getData().isEmpty());
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDataMaxSize() {
+        String startupUrl = "about:blank";
+        mActivity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        Map<String, String> data = new HashMap<>();
+        data.put("big", new String(new char[10000]));
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                mActivity.getTab().setData(data);
+            } catch (IllegalArgumentException e) {
+                // Expected exception.
+                return;
+            }
+            Assert.fail("Expected IllegalArgumentException.");
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testCreateTab() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        CallbackHelper helper = new CallbackHelper();
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            browser.registerTabListCallback(new TabListCallback() {
+                @Override
+                public void onTabAdded(Tab tab) {
+                    helper.notifyCalled();
+                }
+            });
+            Tab newTab = mActivity.getBrowser().createTab();
+            Assert.assertEquals(mActivity.getBrowser().getTabs().size(), 2);
+            Assert.assertNotEquals(newTab, mActivity.getTab());
+            return newTab;
+        });
+        helper.waitForFirst();
+
+        // Make sure the new tab can navigate correctly.
+        mActivityTestRule.navigateAndWait(
+                tab, mActivityTestRule.getTestDataURL("simple_page.html"), false);
+    }
+
+    @Test
+    @SmallTest
+    public void testViewDetachedTabIsInvisible() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+        boolean hidden = mActivityTestRule.executeScriptAndExtractBoolean("document.hidden;");
+        Assert.assertFalse(hidden);
+
+        Fragment fragment = mActivityTestRule.getFragment();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            View fragmentView = fragment.getView();
+            ViewGroup parent = (ViewGroup) fragmentView.getParent();
+            parent.removeView(fragmentView);
+        });
+
+        hidden = mActivityTestRule.executeScriptAndExtractBoolean("document.hidden;");
+        Assert.assertTrue(hidden);
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1248183")
+    // This is a regression test for https://crbug.com/1075744 .
+    public void testRotationDoesntChangeVisibility() throws Exception {
+        String url = mActivityTestRule.getTestDataURL("rotation.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        mActivity.setRetainInstance(true);
+        Assert.assertNotNull(mActivity);
+
+        // Touch to trigger fullscreen and rotation.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+
+        // Wait for the page to be told the orientation changed.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            return mActivityTestRule.executeScriptAndExtractBoolean("gotOrientationChange", false);
+        });
+
+        // The WebContents should not have been hidden as a result of the rotation.
+        Assert.assertFalse(mActivityTestRule.executeScriptAndExtractBoolean("gotHide", false));
+    }
+
+    @Test
+    @SmallTest
+    public void setFloatingActionModeOverride() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getBrowser().getActiveTab().setFloatingActionModeOverride(
+                    ActionModeItemType.SHARE, new ActionModeCallback() {
+                        @Override
+                        public void onActionItemClicked(
+                                @ActionModeItemType int item, String selectedText) {}
+                    });
+        });
+
+        // Smoke test. It's not possible to trigger an action mode click in a test.
+    }
+
+    @Test
+    @SmallTest
+    public void testWillAutomaticallyReloadAfterCrash() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        Browser browser2 = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Browser browser = mActivity.getBrowser();
+            // Initial tab is foreground, so it won't automatically reload.
+            Tab initialTab = browser.getActiveTab();
+            Assert.assertFalse(initialTab.willAutomaticallyReloadAfterCrash());
+
+            // New tab is background, so it will automatically reload.
+            Tab newTab = browser.createTab();
+            Assert.assertEquals(browser.getTabs().size(), 2);
+            Assert.assertNotEquals(newTab, mActivity.getTab());
+            Assert.assertNotEquals(newTab, browser.getActiveTab());
+            Assert.assertTrue(newTab.willAutomaticallyReloadAfterCrash());
+
+            // New tab is foreground after being made active.
+            browser.setActiveTab(newTab);
+            Assert.assertEquals(newTab, browser.getActiveTab());
+            Assert.assertFalse(newTab.willAutomaticallyReloadAfterCrash());
+            Assert.assertTrue(initialTab.willAutomaticallyReloadAfterCrash());
+
+            // Add a second browser; both browsers can have tabs that think they're foreground.
+            Browser newBrowser =
+                    Browser.fromFragment(mActivity.createBrowserFragment(android.R.id.content));
+            Assert.assertTrue(initialTab.willAutomaticallyReloadAfterCrash());
+            Assert.assertFalse(newTab.willAutomaticallyReloadAfterCrash());
+            Assert.assertFalse(newBrowser.getActiveTab().willAutomaticallyReloadAfterCrash());
+
+            // Moving the activity to the background causes all tabs to be not foreground.
+            mActivity.moveTaskToBack(true);
+            return newBrowser;
+        });
+
+        CriteriaHelper.pollUiThread(
+                ()
+                        -> Criteria.checkThat(mActivity.getBrowser()
+                                                      .getActiveTab()
+                                                      .willAutomaticallyReloadAfterCrash(),
+                                Matchers.is(true)));
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> Assert.assertTrue(
+                                browser2.getActiveTab().willAutomaticallyReloadAfterCrash()));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TestFullscreenCallback.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TestFullscreenCallback.java
new file mode 100644
index 0000000..1e7b90d4
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TestFullscreenCallback.java
@@ -0,0 +1,73 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.FullscreenCallback;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * FullscreenCallback implementation for tests.
+ */
+public class TestFullscreenCallback extends FullscreenCallback {
+    public int mEnterFullscreenCount;
+    public int mExitFullscreenCount;
+    public Runnable mExitFullscreenRunnable;
+    private int mCallCountToWaitFor;
+    private final InstrumentationActivityTestRule mTestRule;
+    private final CallbackHelper mCallbackHelper;
+
+    public TestFullscreenCallback(InstrumentationActivityTestRule testRule) {
+        mTestRule = testRule;
+        mCallbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTestRule.getActivity().getTab().setFullscreenCallback(this); });
+    }
+
+    @Override
+    public void onEnterFullscreen(Runnable exitFullscreenRunner) {
+        mEnterFullscreenCount++;
+        mExitFullscreenRunnable = exitFullscreenRunner;
+        mCallbackHelper.notifyCalled();
+    }
+
+    @Override
+    public void onExitFullscreen() {
+        mExitFullscreenCount++;
+        mCallbackHelper.notifyCalled();
+    }
+
+    public void waitForFullscreen() {
+        waitForFullscreenImpl(true);
+    }
+
+    public void waitForExitFullscreen() {
+        waitForFullscreenImpl(false);
+    }
+
+    private void waitForFullscreenImpl(boolean isFullscreen) {
+        try {
+            mCallbackHelper.waitForCallback(mCallCountToWaitFor++);
+        } catch (TimeoutException e) {
+            Assert.fail("Timeout waiting for fullscreen change");
+            return;
+        }
+        // Handles tests that destroy tab.
+        if (mTestRule.getActivity().getTab() == null) return;
+        CriteriaHelper.pollInstrumentationThread(
+                () -> { Criteria.checkThat(isPageFullscreen(), Matchers.is(isFullscreen)); });
+    }
+
+    private boolean isPageFullscreen() {
+        return mTestRule.executeScriptAndExtractBoolean("document.fullscreenElement != null");
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TranslateTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TranslateTest.java
new file mode 100644
index 0000000..9fff05da
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TranslateTest.java
@@ -0,0 +1,146 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Tab;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Basic tests to make sure WebLayer works as expected.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class TranslateTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private InstrumentationActivity mActivity;
+    private Context mRemoteContext;
+    private String mPackageName;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        mRemoteContext = TestWebLayer.getRemoteContext(mActivity.getApplicationContext());
+        mPackageName =
+                TestWebLayer.getWebLayerContext(mActivity.getApplicationContext()).getPackageName();
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+        testWebLayer.setIgnoreMissingKeyForTranslateManager(true);
+        testWebLayer.forceNetworkConnectivityState(true);
+    }
+
+    @Test
+    @SmallTest
+    public void testCanTranslate() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse(mActivity.getBrowser().getActiveTab().canTranslate());
+        });
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("fr_test.html"));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { Assert.assertTrue(mActivity.getBrowser().getActiveTab().canTranslate()); });
+    }
+
+    @Test
+    @SmallTest
+    public void testShowTranslateUi() throws Exception {
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("fr_test.html"));
+        waitForInfoBarToShow();
+        Assert.assertEquals("English", getInfoBarTargetLanguage());
+
+        EventUtils.simulateTouchCenterOfView(findViewByStringId("id/infobar_close_button"));
+        waitForInfoBarToHide();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowser().getActiveTab().showTranslateUi(); });
+        waitForInfoBarToShow();
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "https://crbug.com/1261207")
+    public void testOverridingOfTargetLanguage() throws Exception {
+        // Sanity-check that by default the infobar appears with the target language of the user's
+        // locale.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("french_page.html"));
+        waitForInfoBarToShow();
+        Assert.assertEquals("English", getInfoBarTargetLanguage());
+
+        EventUtils.simulateTouchCenterOfView(findViewByStringId("id/infobar_close_button"));
+        waitForInfoBarToHide();
+
+        // Verify overriding of the target language.
+        Tab tab = mActivityTestRule.getActivity().getTab();
+        TestThreadUtils.runOnUiThreadBlocking(() -> { tab.setTranslateTargetLanguage("de"); });
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("french_page.html"));
+        waitForInfoBarToShow();
+        Assert.assertEquals("German", getInfoBarTargetLanguage());
+
+        EventUtils.simulateTouchCenterOfView(findViewByStringId("id/infobar_close_button"));
+        waitForInfoBarToHide();
+
+        // Check that the setting persists in the Tab by navigating to another page in French via a
+        // link click.
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link_to_french_page2').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+        waitForInfoBarToShow();
+        Assert.assertEquals("German", getInfoBarTargetLanguage());
+
+        EventUtils.simulateTouchCenterOfView(findViewByStringId("id/infobar_close_button"));
+        waitForInfoBarToHide();
+
+        // Check that setting an empty string as the predefined target language causes behavior to
+        // revert to default.
+        TestThreadUtils.runOnUiThreadBlocking(() -> { tab.setTranslateTargetLanguage(""); });
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("french_page.html"));
+        waitForInfoBarToShow();
+        Assert.assertEquals("English", getInfoBarTargetLanguage());
+    }
+
+    private View findViewByStringId(String id) {
+        return mActivity.findViewById(ResourceUtil.getIdentifier(mRemoteContext, id, mPackageName));
+    }
+
+    private void waitForInfoBarToShow() {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(findViewByStringId("id/weblayer_translate_infobar_content"),
+                    Matchers.notNullValue());
+        });
+    }
+
+    private void waitForInfoBarToHide() {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(findViewByStringId("id/weblayer_translate_infobar_content"),
+                    Matchers.nullValue());
+        });
+    }
+
+    private String getInfoBarTargetLanguage() throws Exception {
+        TestWebLayer testWebLayer = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext());
+        return TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return testWebLayer.getTranslateInfoBarTargetLanguage(
+                    mActivity.getBrowser().getActiveTab());
+        });
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebAuthnTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebAuthnTest.java
new file mode 100644
index 0000000..cbdb7ed
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebAuthnTest.java
@@ -0,0 +1,89 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Build;
+import android.os.RemoteException;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.weblayer.TabCallback;
+import org.chromium.weblayer.TestWebLayer;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests WebLayer's implementation of the WebAuthn API.
+ */
+@CommandLineFlags.
+Add({ContentSwitches.HOST_RESOLVER_RULES + "=\"MAP * 127.0.0.1\"", "ignore-certificate-errors"})
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@MinAndroidSdkLevel(Build.VERSION_CODES.N) // WebAuthn is only supported on Android N+
+public class WebAuthnTest {
+    // Should match the domain specified in authenticator.html
+    private static final String TEST_DOMAIN = "subdomain.example.test";
+    private static final String TEST_FILE = "/content/test/data/android/authenticator.html";
+
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+    private InstrumentationActivity mActivity;
+
+    private static class TitleWatcher extends TabCallback {
+        private final CallbackHelper mCallbackHelper = new CallbackHelper();
+        private String mTitle;
+
+        @Override
+        public void onTitleUpdated(String title) {
+            mTitle = title;
+            mCallbackHelper.notifyCalled();
+        }
+
+        public String waitForTitleChange() throws TimeoutException {
+            if (mTitle == null) {
+                mCallbackHelper.waitForCallback(mCallbackHelper.getCallCount());
+            }
+            return mTitle;
+        }
+    }
+
+    @Before
+    public void setUp() throws RemoteException {
+        mActivityTestRule.getTestServerRule().setServerUsesHttps(true);
+        mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
+        TestWebLayer.getTestWebLayer(mActivity.getApplicationContext())
+                .setMockWebAuthnEnabled(true);
+    }
+
+    @Test
+    @MediumTest
+    public void testCreatePublicKeyCredential() throws Exception {
+        String url = mActivityTestRule.getTestServer().getURLWithHostName(TEST_DOMAIN, TEST_FILE);
+        mActivityTestRule.navigateAndWait(url);
+        TitleWatcher titleWatcher = new TitleWatcher();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().registerTabCallback(titleWatcher);
+            mActivity.getTab().executeScript(
+                    "doCreatePublicKeyCredential()", false /* useSeparateIsolate */, null);
+        });
+
+        String title = titleWatcher.waitForTitleChange();
+        assertEquals("Success", title);
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerActivityTestRule.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerActivityTestRule.java
new file mode 100644
index 0000000..a7de5c43
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerActivityTestRule.java
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.test.BaseActivityTestRule;
+
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Base ActivityTestRule for WebLayer instrumentation tests.
+ *
+ * This rule contains some common setup needed to deal with WebLayer's multiple classloaders.
+ */
+abstract class WebLayerActivityTestRule<T extends Activity> extends BaseActivityTestRule<T> {
+    private static final String COMMAND_LINE_FILE = "weblayer-command-line";
+
+    public WebLayerActivityTestRule(Class<T> clazz) {
+        super(clazz);
+    }
+
+    /**
+     * Writes the command line file. This can be useful if a test needs to dynamically add command
+     * line arguments before WebLayer has been loaded.
+     */
+    public void writeCommandLineFile() throws Exception {
+        // The CommandLine instance we have here will not be picked up in the
+        // implementation since they use different class loaders, so we need to write
+        // all the switches to the WebLayer command line file.
+        try (Writer writer = new OutputStreamWriter(
+                     InstrumentationRegistry.getInstrumentation().getTargetContext().openFileOutput(
+                             COMMAND_LINE_FILE, Context.MODE_PRIVATE),
+                     "UTF-8")) {
+            writer.write(TextUtils.join(" ", CommandLine.getJavaSwitchesOrNull()));
+        }
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    writeCommandLineFile();
+                    base.evaluate();
+                } finally {
+                    new File(InstrumentationRegistry.getInstrumentation()
+                                     .getTargetContext()
+                                     .getFilesDir(),
+                            COMMAND_LINE_FILE)
+                            .delete();
+                }
+            }
+        };
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.java
new file mode 100644
index 0000000..e19b5de
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.java
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.runners.model.InitializationError;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.SkipCheck;
+import org.chromium.ui.test.util.UiDisableIfSkipCheck;
+
+import java.util.List;
+
+/**
+ * A custom runner for //weblayer JUnit4 tests.
+ */
+public class WebLayerJUnit4ClassRunner extends BaseJUnit4ClassRunner {
+    /**
+     * Create a WebLayerJUnit4ClassRunner to run {@code klass} and initialize values
+     *
+     * @throws InitializationError if the test class malformed
+     */
+    public WebLayerJUnit4ClassRunner(final Class<?> klass) throws InitializationError {
+        super(klass);
+    }
+
+    @Override
+    protected List<SkipCheck> getSkipChecks() {
+        return addToList(super.getSkipChecks(), new MinWebLayerVersionSkipCheck(),
+                new UiDisableIfSkipCheck(InstrumentationRegistry.getContext()));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerLoadingTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerLoadingTest.java
new file mode 100644
index 0000000..b49835d
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerLoadingTest.java
@@ -0,0 +1,142 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Callback;
+import org.chromium.weblayer.UnsupportedVersionException;
+import org.chromium.weblayer.WebLayer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests for {@link Weblayer#createAsync} and {@link Weblayer#createSync}.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class WebLayerLoadingTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        ContextUtils.initApplicationContextForTests(mContext);
+    }
+
+    @Test
+    @SmallTest
+    public void loadsSync() {
+        assertNotNull(loadSync());
+    }
+
+    @Test
+    @SmallTest
+    public void loadsAsync() {
+        loadAsyncAndWait(webLayer -> { assertNotNull(webLayer); });
+    }
+
+    @Test
+    @SmallTest
+    public void twoSequentialAsyncLoadsYieldSameInstance() {
+        loadAsyncAndWait(webLayer1 -> {
+            loadAsyncAndWait(webLayer2 -> { assertEquals(webLayer1, webLayer2); });
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void twoParallelAsyncLoadsYieldSameInstance() {
+        List<WebLayer> asyncResults = new ArrayList<>();
+        for (int i = 0; i < 2; i++) {
+            loadAsyncAndWait(webLayer -> { asyncResults.add(webLayer); });
+        }
+        assertEquals(asyncResults.get(0), asyncResults.get(1));
+    }
+
+    @Test
+    @SmallTest
+    public void syncLoadAfterAsyncLoadYieldsTheSameInstance() {
+        loadAsyncAndWait(webLayer1 -> {
+            WebLayer webLayer2 = loadSync();
+            assertEquals(webLayer1, webLayer2);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void asyncLoadAfterSyncLoadYieldsTheSameInstance() {
+        WebLayer webLayer1 = loadSync();
+        loadAsyncAndWait(webLayer2 -> { assertEquals(webLayer1, webLayer2); });
+    }
+
+    @Test
+    @SmallTest
+    public void syncLoadDuringAsyncLoadYieldsTheSameInstance() throws Exception {
+        List<WebLayer> asyncResults = new ArrayList<>();
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                WebLayer.loadAsync(mContext, webLayer -> {
+                    asyncResults.add(webLayer);
+                    callbackHelper.notifyCalled();
+                });
+            } catch (UnsupportedVersionException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        WebLayer webLayer2 = loadSync();
+        callbackHelper.waitForFirst();
+        assertEquals(asyncResults.get(0), webLayer2);
+    }
+
+    @Test
+    @SmallTest
+    public void twoSyncLoadsYieldSameInstance() {
+        assertEquals(loadSync(), loadSync());
+    }
+
+    private WebLayer loadSync() {
+        try {
+            return TestThreadUtils.runOnUiThreadBlocking(() -> WebLayer.loadSync(mContext));
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e.getCause());
+        }
+    }
+
+    private void loadAsyncAndWait(Callback<WebLayer> callback) {
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                WebLayer.loadAsync(mContext, webLayer -> {
+                    callback.onResult(webLayer);
+                    callbackHelper.notifyCalled();
+                });
+            } catch (UnsupportedVersionException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        try {
+            callbackHelper.waitForFirst();
+        } catch (TimeoutException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerTest.java
new file mode 100644
index 0000000..8565818
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebLayerTest.java
@@ -0,0 +1,80 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.components.browser_ui.share.ShareImageFileUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests for the WebLayer class.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class WebLayerTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    private class GenerateUriCallback extends CallbackHelper implements Callback<Uri> {
+        private Uri mImageUri;
+
+        public Uri getImageUri() {
+            return mImageUri;
+        }
+
+        @Override
+        public void onResult(Uri uri) {
+            mImageUri = uri;
+            notifyCalled();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void getUserAgentString() {
+        final String userAgent = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return mActivityTestRule.getWebLayer().getUserAgentString(); });
+        Assert.assertNotNull(userAgent);
+        Assert.assertFalse(userAgent.isEmpty());
+    }
+
+    @MinWebLayerVersion(94)
+    @Test
+    @SmallTest
+    public void deletesTemporaryImagesOnInit() throws TimeoutException {
+        // Create a temporary image to simulate an old file left from a previous share.
+        GenerateUriCallback imageCallback = new GenerateUriCallback();
+        ShareImageFileUtils.generateTemporaryUriFromData(new byte[] {1, 2}, ".png", imageCallback);
+        imageCallback.waitForCallback(0, 1, 30L, TimeUnit.SECONDS);
+        File imageFile = new File(imageCallback.getImageUri().getPath());
+
+        // Verify that the file now exists.
+        Assert.assertTrue(imageFile.exists());
+
+        // Create a WebLayer instance which should delete any previously generated images.
+        mActivityTestRule.getWebLayer();
+
+        // Verify that the file has been deleted. Note that this happens in the background.
+        CriteriaHelper.pollInstrumentationThread(
+                () -> Criteria.checkThat(imageFile.exists(), Matchers.is(false)));
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebMessageTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebMessageTest.java
new file mode 100644
index 0000000..932bf11
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebMessageTest.java
@@ -0,0 +1,260 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.WebLayer;
+import org.chromium.weblayer.WebMessage;
+import org.chromium.weblayer.WebMessageCallback;
+import org.chromium.weblayer.WebMessageReplyProxy;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Verifies WebMessage related APIs.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class WebMessageTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    // WebMessageCallback that stores last values supplied to onWebMessageReceived().
+    private static final class WebMessageCallbackImpl extends WebMessageCallback {
+        public WebMessageReplyProxy mLastProxy;
+        public WebMessage mLastMessage;
+        public CallbackHelper mCallbackHelper;
+        public CallbackHelper mClosedCallbackHelper;
+        public CallbackHelper mActiveChangedCallbackHelper;
+        public WebMessageReplyProxy mProxyClosed;
+
+        WebMessageCallbackImpl(CallbackHelper callbackHelper) {
+            mCallbackHelper = callbackHelper;
+        }
+        @Override
+        public void onWebMessageReceived(WebMessageReplyProxy replyProxy, WebMessage message) {
+            mLastProxy = replyProxy;
+            mLastMessage = message;
+            mCallbackHelper.notifyCalled();
+        }
+
+        public void reset() {
+            mLastProxy = null;
+            mLastMessage = null;
+            mProxyClosed = null;
+        }
+
+        @Override
+        public void onWebMessageReplyProxyClosed(WebMessageReplyProxy replyProxy) {
+            mProxyClosed = replyProxy;
+            if (mClosedCallbackHelper != null) mClosedCallbackHelper.notifyCalled();
+        }
+
+        @Override
+        public void onWebMessageReplyProxyActiveStateChanged(WebMessageReplyProxy replyProxy) {
+            if (mActiveChangedCallbackHelper != null) mActiveChangedCallbackHelper.notifyCalled();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testPostMessage() throws Exception {
+        // Load a page with a form.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        assertNotNull(activity);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        WebMessageCallbackImpl webMessageCallback = new WebMessageCallbackImpl(callbackHelper);
+        runOnUiThreadBlocking(() -> {
+            activity.getTab().registerWebMessageCallback(
+                    webMessageCallback, "x", Arrays.asList("*"));
+        });
+
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestDataURL("web_message_test.html"));
+        // web_message_test.html posts a message, wait for it.
+        callbackHelper.waitForCallback(0);
+        assertNotNull(webMessageCallback.mLastMessage);
+        assertNotNull(webMessageCallback.mLastProxy);
+        assertEquals("from page", webMessageCallback.mLastMessage.getContents());
+        final WebMessageReplyProxy lastProxy = webMessageCallback.mLastProxy;
+        int majorVersion = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> WebLayer.getSupportedMajorVersion(mActivityTestRule.getActivity()));
+        if (majorVersion >= 99) {
+            assertNotNull(runOnUiThreadBlocking(() -> { return lastProxy.getPage(); }));
+        }
+        webMessageCallback.reset();
+
+        int currentCallCount = callbackHelper.getCallCount();
+        // Send a message, which the page should ack back.
+        runOnUiThreadBlocking(() -> { lastProxy.postMessage(new WebMessage("Z")); });
+        callbackHelper.waitForCallback(currentCallCount);
+        assertNotNull(webMessageCallback.mLastMessage);
+        assertEquals("bouncing Z", webMessageCallback.mLastMessage.getContents());
+        assertEquals(lastProxy, webMessageCallback.mLastProxy);
+
+        runOnUiThreadBlocking(() -> { activity.getTab().unregisterWebMessageCallback("x"); });
+    }
+
+    @Test
+    @SmallTest
+    public void testOnWebMessageReplyProxyClosed() throws Exception {
+        // Load a page with a form.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        assertNotNull(activity);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        WebMessageCallbackImpl webMessageCallback = new WebMessageCallbackImpl(callbackHelper);
+        runOnUiThreadBlocking(() -> {
+            activity.getTab().registerWebMessageCallback(
+                    webMessageCallback, "x", Arrays.asList("*"));
+        });
+
+        int majorVersion = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> WebLayer.getSupportedMajorVersion(mActivityTestRule.getActivity()));
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestDataURL("web_message_test.html"));
+        // web_message_test.html posts a message, wait for it.
+        callbackHelper.waitForCallback(0);
+        assertNotNull(webMessageCallback.mLastMessage);
+        assertNotNull(webMessageCallback.mLastProxy);
+        WebMessageReplyProxy proxy = webMessageCallback.mLastProxy;
+        assertNull(webMessageCallback.mProxyClosed);
+        assertFalse(
+                runOnUiThreadBlocking(() -> { return webMessageCallback.mLastProxy.isClosed(); }));
+        if (majorVersion >= 90) {
+            assertTrue(runOnUiThreadBlocking(
+                    () -> { return webMessageCallback.mLastProxy.isActive(); }));
+        }
+        webMessageCallback.reset();
+        webMessageCallback.mClosedCallbackHelper = new CallbackHelper();
+
+        // Navigate to a new page. The proxy should be closed.
+        mActivityTestRule.navigateAndWait("about:blank");
+        webMessageCallback.mClosedCallbackHelper.waitForFirst();
+        assertNotNull(webMessageCallback.mProxyClosed);
+        assertEquals(webMessageCallback.mProxyClosed, proxy);
+        assertTrue(runOnUiThreadBlocking(() -> { return proxy.isClosed(); }));
+        if (majorVersion >= 90) {
+            assertFalse(runOnUiThreadBlocking(() -> { return proxy.isActive(); }));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testBadArguments() throws Exception {
+        // Load a page with a form.
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        assertNotNull(activity);
+        WebMessageCallback webMessageCallback = new WebMessageCallback() {};
+        runOnUiThreadBlocking(() -> {
+            // Invalid JS object name.
+            try {
+                activity.getTab().registerWebMessageCallback(
+                        webMessageCallback, "", Arrays.asList("*"));
+                fail();
+            } catch (IllegalArgumentException e) {
+            }
+
+            // Origins can not have empty strings.
+            try {
+                activity.getTab().registerWebMessageCallback(
+                        webMessageCallback, "x", Arrays.asList(""));
+                fail();
+            } catch (IllegalArgumentException e) {
+            }
+
+            // Origins can not be empty.
+            try {
+                activity.getTab().registerWebMessageCallback(
+                        webMessageCallback, "x", new ArrayList<String>());
+                fail();
+            } catch (IllegalArgumentException e) {
+            }
+
+            // Invalid origin.
+            try {
+                activity.getTab().registerWebMessageCallback(
+                        webMessageCallback, "x", Arrays.asList("***"));
+                fail();
+            } catch (IllegalArgumentException e) {
+            }
+        });
+    }
+
+    /* Disable BackForwardCacheMemoryControls to allow BackForwardCache for all devices regardless
+     * of their memory. */
+    @MinWebLayerVersion(90)
+    @Test
+    @SmallTest
+    @CommandLineFlags.
+    Add({"enable-features=BackForwardCache", "disable-features=BackForwardCacheMemoryControls"})
+    public void onActiveChangedForBackForwardCache() throws Exception {
+        TestWebServer testServer = TestWebServer.start();
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+        assertNotNull(activity);
+
+        // Load a page with a message proxy and wait for the respond.
+        CallbackHelper callbackHelper = new CallbackHelper();
+        WebMessageCallbackImpl webMessageCallback = new WebMessageCallbackImpl(callbackHelper);
+        String url = mActivityTestRule.getTestDataURL("web_message_test.html");
+        int index = url.indexOf("/weblayer");
+        assertNotEquals(-1, index);
+        runOnUiThreadBlocking(() -> {
+            activity.getTab().registerWebMessageCallback(
+                    webMessageCallback, "x", Arrays.asList(url.substring(0, index)));
+        });
+        mActivityTestRule.navigateAndWait(
+                mActivityTestRule.getTestDataURL("web_message_test.html"));
+        callbackHelper.waitForFirst();
+
+        // There should be a proxy and it should be active.
+        WebMessageReplyProxy proxy = webMessageCallback.mLastProxy;
+        assertTrue(
+                runOnUiThreadBlocking(() -> { return webMessageCallback.mLastProxy.isActive(); }));
+        webMessageCallback.reset();
+        webMessageCallback.mActiveChangedCallbackHelper = new CallbackHelper();
+
+        // Navigate to a new page. The proxy should be inactive, but not closed.
+        String url2 = testServer.setResponse("/ok.html", "<html>ok</html>", null);
+        mActivityTestRule.navigateAndWait(url2);
+        webMessageCallback.mActiveChangedCallbackHelper.waitForCallback(0);
+        assertFalse(runOnUiThreadBlocking(() -> { return proxy.isActive(); }));
+        assertFalse(runOnUiThreadBlocking(() -> { return proxy.isClosed(); }));
+
+        // Navigate back and ensure the page is active.
+        runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
+        webMessageCallback.mActiveChangedCallbackHelper.waitForCallback(1);
+        assertTrue(runOnUiThreadBlocking(() -> { return proxy.isActive(); }));
+        assertFalse(runOnUiThreadBlocking(() -> { return proxy.isClosed(); }));
+
+        // Post a message, to ensure the page can still get it.
+        webMessageCallback.reset();
+        webMessageCallback.mCallbackHelper = new CallbackHelper();
+        runOnUiThreadBlocking(() -> { proxy.postMessage(new WebMessage("2")); });
+        webMessageCallback.mCallbackHelper.waitForFirst();
+        assertNotNull(webMessageCallback.mLastMessage);
+        assertEquals("bouncing 2", webMessageCallback.mLastMessage.getContents());
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebViewCompatibilityTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebViewCompatibilityTest.java
new file mode 100644
index 0000000..a901610
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/WebViewCompatibilityTest.java
@@ -0,0 +1,84 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import android.os.Bundle;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.StrictModeContext;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.shell.InstrumentationActivity;
+
+/**
+ * Tests for compatibility with running WebView and WebLayer in the same process. These tests only
+ * make sense when WebView and WebLayer are both being loaded from the same APK.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+public class WebViewCompatibilityTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "http://crbug.com/1273417") // Failing on android-arm64-proguard-rel
+    public void testBothLoadPageWebLayerFirst() throws Exception {
+        mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html"));
+
+        loadPageWithWebView(mActivityTestRule.getTestDataURL("simple_page2.html"));
+
+        // Make sure WebLayer still loads.
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("simple_page3.html"));
+    }
+
+    @Test
+    @SmallTest
+    @DisabledTest(message = "http://crbug.com/1273417") // Failing on android-arm64-proguard-rel
+    public void testBothLoadPageWebViewFirst() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putBoolean(InstrumentationActivity.EXTRA_CREATE_WEBLAYER, false);
+        InstrumentationActivity activity = mActivityTestRule.launchShell(extras);
+
+        loadPageWithWebView(mActivityTestRule.getTestDataURL("simple_page.html"));
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> activity.loadWebLayerSync(activity.getApplicationContext()));
+        mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL("simple_page2.html"));
+
+        // Make sure WebView still loads.
+        loadPageWithWebView(mActivityTestRule.getTestDataURL("simple_page3.html"));
+    }
+
+    private void loadPageWithWebView(String urlToLoad) throws Exception {
+        WebView webView = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Loading WebView triggers loading from disk.
+            try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+                return new WebView(mActivityTestRule.getActivity());
+            }
+        });
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            webView.setWebViewClient(new WebViewClient() {
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    Assert.assertEquals(url, urlToLoad);
+                    callbackHelper.notifyCalled();
+                }
+            });
+            webView.loadUrl(urlToLoad);
+        });
+        callbackHelper.waitForFirst();
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/XClientDataTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/XClientDataTest.java
new file mode 100644
index 0000000..63b176de
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/XClientDataTest.java
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer.test;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+/**
+ * WebLayer tests that need to use WebLayerPrivate.
+ */
+@RunWith(WebLayerJUnit4ClassRunner.class)
+@CommandLineFlags.Add({"force-variation-ids=4,10,34"})
+public class XClientDataTest {
+    @Rule
+    public InstrumentationActivityTestRule mActivityTestRule =
+            new InstrumentationActivityTestRule();
+
+    @MinWebLayerVersion(101)
+    @Test
+    @SmallTest
+    public void getXClientDataHeader() {
+        final String header = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return mActivityTestRule.getWebLayer().getXClientDataHeader(); });
+        Assert.assertNotNull(header);
+        Assert.assertFalse(header.isEmpty());
+    }
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
index 7c00999..a851229 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
@@ -79,6 +79,7 @@
 import org.chromium.weblayer_private.interfaces.ITabClient;
 import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient;
 import org.chromium.weblayer_private.interfaces.ObjectWrapper;
+import org.chromium.weblayer_private.interfaces.RestrictedAPIException;
 import org.chromium.weblayer_private.interfaces.ScrollNotificationType;
 import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
 import org.chromium.weblayer_private.media.MediaSessionManager;
@@ -764,8 +765,7 @@
             assert unwrappedCallback != null;
 
             if (!verified) {
-                // TODO(crbug.com/1392110): Pass a RestrictedAPIException.
-                unwrappedCallback.onReceiveValue(null);
+                throw new RestrictedAPIException();
             }
 
             Callback<String> nativeCallback = new Callback<String>() {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
index 63df5ee..1f40103 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
@@ -254,11 +254,6 @@
         mIsWebViewCompatMode = remoteContext != null
                 && !remoteContext.getClassLoader().equals(WebLayerImpl.class.getClassLoader());
         if (mIsWebViewCompatMode) {
-            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
-                // Load the library with the crazy linker.
-                LibraryLoader.getInstance().setLinkerImplementation(true, false);
-                WebViewCompatibilityHelperImpl.setRequiresManualJniRegistration(true);
-            }
             notifyWebViewRunningInProcess(remoteContext.getClassLoader());
         }
 
diff --git a/weblayer/public/java/BUILD.gn b/weblayer/public/java/BUILD.gn
index 3abed3f..36c17961 100644
--- a/weblayer/public/java/BUILD.gn
+++ b/weblayer/public/java/BUILD.gn
@@ -212,7 +212,10 @@
 }
 
 android_library("webengine_interfaces_java") {
-  sources = [ "org/chromium/webengine/interfaces/ExceptionType.java" ]
+  sources = [
+    "org/chromium/webengine/interfaces/ExceptionType.java",
+    "org/chromium/webengine/interfaces/RestrictedAPIException.java",
+  ]
   deps = [
     "//base:base_java",
     "//third_party/androidx:androidx_annotation_annotation_jvm_java",
@@ -229,7 +232,6 @@
     "org/chromium/webengine/Navigation.java",
     "org/chromium/webengine/NavigationObserver.java",
     "org/chromium/webengine/NavigationObserverDelegate.java",
-    "org/chromium/webengine/RestrictedAPIException.java",
     "org/chromium/webengine/Tab.java",
     "org/chromium/webengine/TabListObserver.java",
     "org/chromium/webengine/TabListObserverDelegate.java",
diff --git a/weblayer/public/java/org/chromium/webengine/ExceptionHelper.java b/weblayer/public/java/org/chromium/webengine/ExceptionHelper.java
index 38e934b..adc8551 100644
--- a/weblayer/public/java/org/chromium/webengine/ExceptionHelper.java
+++ b/weblayer/public/java/org/chromium/webengine/ExceptionHelper.java
@@ -5,6 +5,7 @@
 package org.chromium.webengine;
 
 import org.chromium.webengine.interfaces.ExceptionType;
+import org.chromium.webengine.interfaces.RestrictedAPIException;
 
 class ExceptionHelper {
     static Exception createException(@ExceptionType int type, String msg) {
diff --git a/weblayer/public/java/org/chromium/webengine/RestrictedAPIException.java b/weblayer/public/java/org/chromium/webengine/interfaces/RestrictedAPIException.java
similarity index 88%
rename from weblayer/public/java/org/chromium/webengine/RestrictedAPIException.java
rename to weblayer/public/java/org/chromium/webengine/interfaces/RestrictedAPIException.java
index 1732396..860d74c 100644
--- a/weblayer/public/java/org/chromium/webengine/RestrictedAPIException.java
+++ b/weblayer/public/java/org/chromium/webengine/interfaces/RestrictedAPIException.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.webengine;
+package org.chromium.webengine.interfaces;
 
 /**
  * Error thrown for API access violations.
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index 4f28fea..c32b4ca 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -257,6 +257,8 @@
             mImpl.executeScript(script, useSeparateIsolate, ObjectWrapper.wrap(callback));
         } catch (RemoteException e) {
             throw new APICallException(e);
+        } catch (RuntimeException e) {
+            ExceptionHelper.reraise(e);
         }
     }
 
diff --git a/weblayer/public/java/org/chromium/weblayer/TabProxy.java b/weblayer/public/java/org/chromium/weblayer/TabProxy.java
index e300c10..a55f65c7 100644
--- a/weblayer/public/java/org/chromium/weblayer/TabProxy.java
+++ b/weblayer/public/java/org/chromium/weblayer/TabProxy.java
@@ -13,6 +13,7 @@
 import org.chromium.webengine.interfaces.ITabObserverDelegate;
 import org.chromium.webengine.interfaces.ITabProxy;
 import org.chromium.webengine.interfaces.IWebMessageCallback;
+import org.chromium.weblayer_private.interfaces.RestrictedAPIException;
 
 import java.util.List;
 
@@ -77,24 +78,19 @@
     @Override
     public void executeScript(String script, boolean useSeparateIsolate, IStringCallback callback) {
         mHandler.post(() -> {
-            getTab().executeScript(script, useSeparateIsolate, (String result) -> {
-                try {
-                    if (result != null) {
-                        callback.onResult(result);
-                    } else {
-                        // TODO(crbug.com/1392110): Pass a useful exception message.
-                        try {
-                            callback.onException(ExceptionType.RESTRICTED_API, "");
-                        } catch (RemoteException e) {
-                        }
-                    }
-                } catch (RemoteException e) {
+            try {
+                getTab().executeScript(script, useSeparateIsolate, (String result) -> {
                     try {
-                        callback.onException(ExceptionType.UNKNOWN, e.getMessage());
-                    } catch (RemoteException e2) {
+                        callback.onResult(result);
+                    } catch (RemoteException e) {
                     }
+                });
+            } catch (RestrictedAPIException e) {
+                try {
+                    callback.onException(ExceptionType.RESTRICTED_API, e.getMessage());
+                } catch (RemoteException re) {
                 }
-            });
+            }
         });
     }
 
diff --git a/weblayer/shell/android/webengine_shell_apk/res/values/strings.xml b/weblayer/shell/android/webengine_shell_apk/res/values/strings.xml
index 49fed14..153aadc 100644
--- a/weblayer/shell/android/webengine_shell_apk/res/values/strings.xml
+++ b/weblayer/shell/android/webengine_shell_apk/res/values/strings.xml
@@ -12,13 +12,6 @@
           \"namespace\": \"web\",
           \"site\": \"https://example.com\"
         }
-      },
-      {
-        \"relation\": [\"delegate_permission/common.handle_all_urls\"],
-        \"target\": {
-          \"namespace\": \"web\",
-          \"site\": \"http://localhost:8888\"
-        }
       }]
     </string>
 </resources>
\ No newline at end of file
diff --git a/weblayer/shell/android/webengine_shell_apk/src/org/chromium/webengine/shell/InstrumentationActivity.java b/weblayer/shell/android/webengine_shell_apk/src/org/chromium/webengine/shell/InstrumentationActivity.java
index b3ffae8..34fa31d 100644
--- a/weblayer/shell/android/webengine_shell_apk/src/org/chromium/webengine/shell/InstrumentationActivity.java
+++ b/weblayer/shell/android/webengine_shell_apk/src/org/chromium/webengine/shell/InstrumentationActivity.java
@@ -7,11 +7,9 @@
 import android.os.Bundle;
 
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.FragmentManager;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
-import org.chromium.webengine.WebFragment;
 import org.chromium.webengine.WebSandbox;
 
 /**
@@ -31,17 +29,4 @@
     public ListenableFuture<WebSandbox> getWebSandboxFuture() {
         return mWebSandboxFuture;
     }
-
-    public void attachFragment(WebFragment fragment) {
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        fragmentManager.beginTransaction()
-                .setReorderingAllowed(true)
-                .add(R.id.fragment_container_view, fragment)
-                .commitNow();
-    }
-
-    public void detachFragment(WebFragment fragment) {
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        fragmentManager.beginTransaction().setReorderingAllowed(true).remove(fragment).commitNow();
-    }
-}
+}
\ No newline at end of file